RPM 系列文章:
spec 文件
制作 rpm 軟件包並不是一件復雜的工作,其中的關鍵在於編寫軟件包的 spec 描述文件。
要想制作一個 rpm 軟件包就必須寫一個軟件包描述文件 spec。這個文件中包含了軟件包的諸多信息,如:軟件包的名字、版本、類別、說明摘要、創建時要執行什么指令、安裝時要執行什么操作、以及軟件包所要包含的文件列表等等。
實際過程中,最關鍵的地方,是要清楚虛擬路徑的位置,以及宏的定義。
文件頭
這個區域定義的 Name
、Version
這些字段對應的值可以在后面通過 %{name}
,%{version}
這樣的方式來引用,類似於 C 語言中的宏
Summary
:用一句話概括該軟件s包盡量多的信息。Name
:軟件包的名字,最終 rpm 軟件包是用該名字與版本號(Version
)、釋出號(Release
)及體系號來命名軟件包的,后面可使用%{name}
的方式引用Version
:軟件版本號。僅當軟件包比以前有較大改變時才增加版本號,后面可使用%{version}
引用Release
:軟件包釋出號/發行號。一般我們對該軟件包做了一些小的補丁的時候就應該把釋出號加 1,后面可使用%{release}
引用Packager
:打包的人(一般喜歡寫個人郵箱)Vendor
:軟件開發者的名字,發行商或打包組織的信息,例如RedFlagCo,Ltd
License
:軟件授權方式,通常是GPL(自由軟件)或GPLv2,BSDCopyright
:軟件包所采用的版權規則。具體有:GPL(自由軟件)
,BSD
,MIT
,Public Domain(公共域)
,Distributable(貢獻)
,commercial(商業)
,Share(共享)
等,一般的開發都寫GPL
。Group
:軟件包所屬類別- Development/System (開發/系統)
- System Environment/Daemons (系統環境/守護)
Source
:源程序軟件包的名字/源代碼包的名字,如stardict-2.0.tar.gz
。可以帶多個用Source1
、Source2
等源,后面也可以用%{source1}
、%{source2}
引用
Source0: %{name}-boost-%{version}.tar.gz ← 源碼包名稱(可以使用URL),可以用SourceN指定多個,如配置文件
#Patch0: some-bugs0.patch ← 如果需要打補丁,則依次填寫
#Patch1: some-bugs1.patch ← 如果需要打補丁,則依次填寫
BuildRequires
: 制作過程中用到的軟件包,構建依賴Requires
: 安裝時所需軟件包Requires(pre)
: 指定不同階段的依賴
BuildRoot
: 這個是安裝或編譯時使用的「虛擬目錄」,打包時會用到該目錄下文件,可查看安裝后文件路徑,例如:BuildRoot: %_topdir/BUILDROOT
。Prefix: %{_prefix}
這個主要是為了解決今后安裝 rpm 包時,並不一定把軟件安裝到 rpm 中打包的目錄的情況。這樣,必須在這里定義該標識,並在編寫%install
腳本的時候引用,才能實現 rpm 安裝時重新指定位置的功能BuildArch
: 指編譯的目標處理器架構,noarch
標識不指定,但通常都是以/usr/lib/rpm/marcros
中的內容為默認值%description
:軟件包詳細說明,可寫在多個行上。這樣任何人使用rpm -qi
查詢您的軟件包時都可以看到它。您可以解釋這個軟件包做什么,描述任何警告或附加的配置指令,等等。URL
:軟件的主頁
RPM 包信息查看
我通過命令查看了 nginx 包的信息,如下:
# 查看頭部信息
$ rpm -qpi ./nginx-1.12.2-2.el7.x86_64.rpm
Name : nginx
Epoch : 1
Version : 1.12.2
Release : 2.el7
Architecture: x86_64
Install Date: (not installed)
Group : System Environment/Daemons
Size : 1574949
License : BSD
Signature : RSA/SHA256, Tue 06 Mar 2018 05:44:06 PM CST, Key ID 6a2faea2352c64e5
Source RPM : nginx-1.12.2-2.el7.src.rpm
Build Date : Tue 06 Mar 2018 05:27:44 PM CST
Build Host : buildhw-02.phx2.fedoraproject.org
Relocations : (not relocatable)
Packager : Fedora Project
Vendor : Fedora Project
URL : http://nginx.org/
Bug URL : https://bugz.fedoraproject.org/nginx
Summary : A high performance web server and reverse proxy server
Description :
Nginx is a web server and a reverse proxy server for HTTP, SMTP, POP3 and
IMAP protocols, with a strong focus on high concurrency, performance and low
memory usage.
# 查看腳本內容
$ rpm --scripts -qp ./nginx-1.12.2-2.el7.x86_64.rpm
postinstall scriptlet (using /bin/sh):
if [ $1 -eq 1 ] ; then
# Initial installation
systemctl preset nginx.service >/dev/null 2>&1 || :
fi
preuninstall scriptlet (using /bin/sh):
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
systemctl --no-reload disable nginx.service > /dev/null 2>&1 || :
systemctl stop nginx.service > /dev/null 2>&1 || :
fi
postuninstall scriptlet (using /bin/sh):
systemctl daemon-reload >/dev/null 2>&1 || :
if [ $1 -ge 1 ]; then
/usr/bin/nginx-upgrade >/dev/null 2>&1 || :
fi
RPM 包依賴
依賴關系定義了一個包正常工作需要依賴的其他包,RPM在升級、安裝和刪除的時候會確保依賴關系得到滿足。
BuildRequires
定義構建時依賴的軟件包,在構建機器編譯 rpm 包時需要的輔助工具,以逗號分隔。
假如,要求編譯 myapp
時,gcc 的版本至少為 4.4.2,則可以寫成 gcc >=4.2.2
Requires
定義安裝時的依賴包,該 rpm 包所依賴的軟件包名稱,就是指編譯好的 rpm 軟件在其他機器上安裝時,需要依賴的其他軟件包。
可以用 >=
或 <=
表示大於或小於某一特定版本。 >=
號兩邊需用空格隔開,而不同軟件名稱也用空格分開。
格式:
Requires: libpng-devel >= 1.0.20 zlib
其它寫法例如:
Requires: bzip2 = %{version}, bzip2-libs =%{version}
還有例如PreReq
、Requires(pre)
、Requires(post)
、Requires(preun)
、Requires(postun)
、BuildRequires
等都是針對不同階段的依賴指定。
關於pre
、post
、preun
、postun
含義理解,感覺post
有一種“完成”的意思:
# 安裝前執行的腳本,語法和shell腳本的語法相同
%pre
# 安裝后執行的腳本
%post
# 卸載前執行的腳本
%preun
# 卸載完成后執行的腳本
%postun
# 清理階段,在制作完成后刪除安裝的內容
例如:
PreReq: capability>=version #capability包必須先安裝
Conflicts:bash>=2.0 #該包和所有不小於2.0的bash包有沖突
BuildRoot
該參數非常重要,因為在生成 rpm 的過程中,執行 make install
時就會把軟件安裝到上述的路徑中,在打包的時候,同樣依賴“虛擬目錄”為“根目錄”進行操作。后面可使用 $RPM_BUILD_ROOT
方式引用。
考慮到多用戶的環境,一般定義為:
%{_tmppath}/%{name}-%{version}-%{release}-root
# OR
%{_tmppath}/%{name}-%{version}-%{release}-buildroot-%(%{__id_u} -n}
下面介紹 spec 腳本主體:
%prep 階段
這個階段是「預處理」,通常用來執行一些解開源程序包的命令,為下一步的編譯安裝作准備。
%prep
和下面的 %build
,%install
段一樣,除了可以執行 rpm 所定義的宏命令(以%
開頭)以外,還可以執行 SHELL
命令,命令可以有很多行,如我們常寫的 tar
解包命令。功能上類似於 ./configure
。
%prep
階段進行實際的打包准備工作,它是使用節前綴 %prep
表示的。主要功能有:
- 將文件 (
SOURCES/
) 解壓到構建目錄 (BUILD/
) - 應用
Patch
(打補丁) (SOURCES/ => BUILD/
) - 描述
rm -rf $RPM_BUILD_ROOT
- 描述或編輯本部分用到的命令到
PreReq
- 通過
-b .XXX
描述補丁備份
它一般包含 %setup
與 %patch
兩個命令。%setup
用於將軟件包打開,執行 %patch
可將補丁文件加入解開的源程序中。
示例:
%prep
%setup -q ← 宏的作用是解壓並切換到目錄
#%patch0 -p1 ← 如果需要打補丁,則依次寫
#%patch1 -p1 ← 如果需要打補丁,則依次寫
%setup
這個宏解壓源代碼,將當前目錄改為源代碼解壓之后產生的目錄。這個宏還有一些選項可以用。例如,在解壓后,%setup
宏假設產生的目錄是 %{name}-%{version}
%setup -n %{name}-%{version} #把源碼包解壓並放好
主要的用途就是將 %sourcedir
目錄下的源代碼解壓到 %builddir
目錄下,也就是將~/rpmbuild/SOURCES
里的包解壓到 ~/rpmbuild/BUILD/%{name}-%{version}
中。一般用 %setup -c
就可以了,但有兩種情況:
- 一就是同時編譯多個源碼包,
- 二就是源碼的 tar 包的名稱與解壓出來的目錄不一致,此時,就需要使用
-n
參數指定一下。
%setup 不加任何選項,僅將軟件包打開。
%setup -a 切換目錄前,解壓指定 Source 文件,例如 `-a 0` 表示解壓 `Source0`
%setup -n newdir 將軟件包解壓在newdir目錄。
%setup -c 解壓縮之前先產生目錄。
%setup -b num 將第 num 個 source 文件解壓縮。
%setup -D 解壓前不刪除目錄
%setup -T 不使用default的解壓縮操作。
%setup -T -b 0 將第 0 個源代碼文件解壓縮。
%setup -c -n newdir 指定目錄名稱 newdir,並在此目錄產生 rpm 套件。
%patch 最簡單的補丁方式,自動指定patch level。
%patch 0 使用第0個補丁文件,相當於%patch ?p 0。
%patch -s 不顯示打補丁時的信息。
%patch -T 將所有打補丁時產生的輸出文件刪除
應該 -q
參數給 %setup
宏。這會顯著減少編譯日志文件的輸出,尤其是源代碼包會解壓出一堆文件的時候。
在 ~/rmpbuild/BUILD/%{name}-%{version}
目錄中進行 ,使用標准寫法,會引用/usr/lib/rpm/marcros
中定義的參數。
%patch
這個宏將頭部定義的補丁應用於源代碼。如果定義了多個補丁,它可以用一個數字的參數來指示應用哪個補丁文件。它也接受 -b extension
參數,指示 RPM 在打補丁之前,將文件備份為擴展名是 extension
的文件。
通常補丁都會一起在源碼 tar.gz
包中,或放到 SOURCES
目錄下。一般參數為:
%patch -p1
使用前面定義的Patch
補丁進行,-p1
是忽略patch
的第一層目錄%Patch2 -p1 -b xxx.patch
打上指定的補丁,-b
是指生成備份文件
%build 階段
本段是「構建」階段,這個階段會在 %_builddir
目錄下執行源碼包的編譯。一般是執行執行常見的 configure
和 make
操作。
該階段一般由多個 make
命令組成。與 %prep
段落一樣,這些命令可以是 shell
命令,也可以是宏。
記住兩點:
%build
和%install
的過程中,都必須把編譯和安裝的文件定義到“虛擬根目錄” 中%file
中必須明白,用的是相對目錄
這個階段我們最常見只有兩條指令:
%configure
make %{?_smp_mflags} OPTIMIZE="%{optflags}" ← 多核則並行編譯
%configure
這個不是關鍵字,而是 rpm 定義的標准宏命令。意思是執行源代碼的configure
配置。會自動將 prefix
設置成 /usr
。
這個 %{?_smp_mflags} %{optflags}
是什么意思呢?
$ rpm --eval "%{?_smp_mflags}"
-j4
$ rpm --eval "%{optflags}"
-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic
所以,上面那個命令就是 make -j4
。問題又來了,make -j4
表示什么意思?
都是一些優化參數
%install 階段
「安裝」階段,就是執行 make install
命令操作。開始把軟件安裝到虛擬的根目錄中。這個階段會在 %buildrootdir
目錄里建好目錄結構,然后將需要打包到 rpm 軟件包里的文件從 %builddir
里拷貝到 %_buildrootdir
里對應的目錄里。
在 ~/rpmbuild/BUILD/%{name}-%{version}
目錄中進行 make install
的操作。%install
很重要,因為如果這里的路徑不對的話,則下面 %file
中尋找文件的時候就會失敗。
特別需要注意的是:%install
部分使用的是絕對路徑,而 %file
部分使用則是相對路徑,雖然其描述的是同一個地方。千萬不要寫錯。
當用戶最終用 rpm -ivh name-version.rpm
安裝軟件包時,這些文件會安裝到用戶系統中相應的目錄里。
%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/
「翻譯」一下:
make install DESTDIR=/root/rpmbuild/BUILDROOT/%{name}-%{version}-%{release}.x86_64
make install DESTDIR=/root/rpmbuild/BUILDROOT/%{name}-%{version}-%{release}.x86_64
/usr/bin/install -d /root/rpmbuild/BUILDROOT/%{name}-%{version}-%{release}.x86_64/etc/foobar.d/
需要說明的是,這里的 %install
主要就是為了后面的 %file
服務的。所以,還可以使用常規的系統命令:
install -d $RPM_BUILD_ROOT/
cp -a * $RPM_BUILD_ROOT/
其中 %{buildroot}
和 $RPMBUILDROOT
是等價的, %{buildroot}
必須全部用小寫,不然要報錯。
注意區分 $RPM_BUILD_ROOT
和 $RPM_BUILD_DIR
:
$RPM_BUILD_ROOT
是指開頭定義的BuildRoot
,是%file
需要的。$RPM_BUILD_DIR
通常就是指~/rpmbuild/BUILD
scripts section 沒必要可以不填
%pre
安裝前執行的腳本%post
安裝后執行的腳本%preun
卸載前執行的腳本%postun
卸載后執行的腳本%pretrans
在事務開始時執行腳本%posttrans
在事務結束時執行腳本
%preun
%postun
的區別是什么呢?
- 前者在升級的時候會執行,后者在升級 rpm 包的時候不會執行
%files 階段
本段是文件段,主要用來說明會將 %{buildroot}
目錄下的哪些文件和目錄最終打包到rpm包里。定義軟件包所包含的文件,分為三類:
- 說明文檔(doc)
- 配置文件(config)
- 執行程序
還可定義文件存取權限,擁有者及組別。
這里會在虛擬根目錄下進行,千萬不要寫絕對路徑,而應用宏或變量表示相對路徑。
在 %files
階段的第一條命令的語法是:
%defattr(文件權限,用戶名,組名,目錄權限)
注意點:同時需要在 %install
中安裝。
%files
%defattr (-,root,root,0755) ← 設定默認權限
%config(noreplace) /etc/my.cnf ← 表明是配置文件,noplace表示替換文件
%doc %{src_dir}/Docs/ChangeLog ← 表明這個是文檔
%attr(644, root, root) %{_mandir}/man8/mysqld.8* ← 分別是權限,屬主,屬組
%attr(755, root, root) %{_sbindir}/mysqld
%exclude
列出不想打包到 rpm 中的文件。注意:如果 %exclude
指定的文件不存在,也會出錯的。
在安裝 rpm 時,會將可執行的二進制文件放在 /usr/bin
目錄下,動態庫放在 /usr/lib
或者 /usr/lib64
目錄下,配置文件放在 /etc
目錄下,並且多次安裝時新的配置文件不會覆蓋以前已經存在的同名配置文件。
關於 %files
階段有兩個特性:
%{buildroot}
里的所有文件都要明確被指定是否要被打包到 rpm里。什么意思呢?假如,%{buildroot}
目錄下有 4 個目錄 a、b、c和d,在%files
里僅指定 a 和 b 要打包到 rpm 里,如果不把 c 和 d 用exclude
聲明是要報錯的;- 如果聲明了
%{buildroot}
里不存在的文件或者目錄也會報錯。
關於 %doc
宏,所有跟在這個宏后面的文件都來自 %{_builddir}
目錄,當用戶安裝 rpm 時,由這個宏所指定的文件都會安裝到 /usr/share/doc/name-version/
目錄里。
%clean
清理段,可以通過 --clean
刪除 BUILD
編譯完成后一些清理工作,主要包括對 %{buildroot}
目錄的清空(這不是必須的),通常執行諸如 make clean
之類的命令。
%changelog
本段是修改日志段,記錄 spec 的修改日志段。你可以將軟件的每次修改記錄到這里,保存到發布的軟件包中,以便查詢之用。
每一個修改日志都有這樣一種格式:
- 第一行是:
* 星期 月 日 年 修改人 電子信箱
。其中:星期、月份均用英文形式的前 3 個字母,用中文會報錯。 - 接下來的行寫的是修改了什么地方,可寫多行。一般以減號開始,便於后續的查閱。
%changelog
* Fri Dec 29 2012 foobar <foobar@kidding.com> - 1.0.0-1
- Initial version
宏
在定義文件的安裝路徑時,通常會使用類似 %_sharedstatedir
的宏,這些宏一般會在 /usr/lib/rpm/macros
中定義。關於宏的語法,可以查看 Macro syntax
RPM 內建宏定義在 /usr/lib/rpm/redhat/macros
文件中,這些宏基本上定義了目錄路徑或體系結構等等;同時也包含了一組用於調試 spec 文件的宏。
所有宏都可以在 /usr/lib/rpm/macros
找到,附錄一些常見的宏:
%{_sysconfdir} /etc
%{_prefix} /usr
%{_exec_prefix} %{_prefix}
%{_bindir} %{_exec_prefix}/bin
%{_lib} lib (lib64 on 64bit systems)
%{_libdir} %{_exec_prefix}/%{_lib}
%{_libexecdir} %{_exec_prefix}/libexec
%{_sbindir} %{_exec_prefix}/sbin
%{_sharedstatedir} /var/lib
%{_datadir} %{_prefix}/share
%{_includedir} %{_prefix}/include
%{_oldincludedir} /usr/include
%{_infodir} /usr/share/info
%{_mandir} /usr/share/man
%{_localstatedir} /var
%{_initddir} %{_sysconfdir}/rc.d/init.d
%{_topdir} %{getenv:HOME}/rpmbuild
%{_builddir} %{_topdir}/BUILD
%{_rpmdir} %{_topdir}/RPMS
%{_sourcedir} %{_topdir}/SOURCES
%{_specdir} %{_topdir}/SPECS
%{_srcrpmdir} %{_topdir}/SRPMS
%{_buildrootdir} %{_topdir}/BUILDROOT
%{_var} /var
%{_tmppath} %{_var}/tmp
%{_usr} /usr
%{_usrsrc} %{_usr}/src
%{_docdir} %{_datadir}/doc
%{buildroot} %{_buildrootdir}/%{name}-%{version}-%{release}.%{_arch}
$RPM_BUILD_ROOT %{buildroot}
利用 rpmbuild 構建 rpm 安裝包時,通過命令 rpm --showrc|grep prefix
查看。
通過 rpm --eval "%{macro}"
來查看具體對應路徑。
比如我們要查看 %{_bindir}
的路徑,就可以使用命令 rpm --eval "%{ _bindir}"
來查看。
%{_topdir} %{getenv:HOME}/rpmbuild
%{_builddir} %{_topdir}/BUILD
%{_rpmdir} %{_topdir}/RPMS
%{_sourcedir} %{_topdir}/SOURCES
%{_specdir} %{_topdir}/SPECS
%{_srcrpmdir} %{_topdir}/SRPMS
%{_buildrootdir} %{_topdir}/BUILDROOT
Note: On releases older than Fedora 10 (and EPEL), %{_buildrootdir} does not exist.
Build flags macros
%{_global_cflags} -O2 -g -pipe
%{_optflags} %{__global_cflags} -m32 -march=i386 -mtune=pentium4 # if redhat-rpm-config is installed
變量
define
定義的變量類似於局部變量,只在 %{!?foo: ... }
區間有效,不過 SPEC 並不會自動清除該變量,只有再次遇到 %{}
時才會清除
define vs. global
兩者都可以用來進行變量定義,不過在細節上有些許差別,簡單列舉如下:
define
用來定義宏,global
用來定義變量;- 如果定義帶參數的宏 (類似於函數),必須要使用
define
; - 在
%{}
內部,必須要使用global
而非define
; define
在使用時計算其值,而global
則在定義時就計算其值;
可以簡單參考如下的示例。
#--- %prep之前的參數是必須要有的
Name: mysql
Version: 5.7.17
Release: 1%{?dist}
Summary: MySQL from FooBar.
License: GPLv2+ and BSD
%description
It is a MySQL from FooBar.
%prep
#--- 帶參數時,必須使用%define定義
%define myecho() echo %1 %2
%{!?bar: %define bar defined}
echo 1: %{bar}
%{myecho 2: %{bar}}
echo 3: %{bar}
# 如下是輸出內容
#1: defined
#2: defined
#3: %{bar}
3 的輸出是不符合預期的,可以將 %define
修改為 global
即可
%{?dist} 表示什么含義?
不加問號,如果 dist
有定義,那么就會用定義的值替換,否則就會保 %{dist}
;
加問好,如果 dist
有定義,那么也是會用定義的值替換,否則就直接移除這個tag %{?dist}
舉例:
$ rpm -E 'foo:%{foo}'$'\n''bar:%{?bar}'
foo:%{foo}
bar:
$ rpm -D'foo foov' -E 'foo:%{foo}'$'\n''bar:%{?bar}'
foo:foov
bar:
$ rpm -D'foo foov' -D'bar barv' -E 'foo:%{foo}'$'\n''bar:%{?bar}'
foo:foov
bar:barv
小結
看了以上SPEC的總結,以為了解差不多了,結果看了網絡域的SPEC文件,自己真實too young too simple
。
參考
- 開源中國-RPM包的制作 對 rpmbuild 目錄的創建及生成介紹比較詳細
- 夜鳴豬-RPM包rpmbuild SPEC文件深度說明
- OpenSUSE-spec 文件指南
- CSDN-RPM打包原理、示例、詳解及備查 很詳細,有示例
- Fedora-WIKI-How to create an RPM package/zh-cn 中文介紹
- IBM-RPM 打包技術與典型 SPEC 文件分析
- JIN-YANG-RPM 包制作 總結很全,還介紹了簽名
- CSDN-RPM構建 - SPEC文件參數解析 該博主,總共圍繞 RPM 構建寫了幾篇文章的
- 原-關於rpm打包中的條件判斷 介紹了 spec 中條件判斷的語法
- Whats the meaing of 1%{?dist} in SPEC file in RPM package