RPM 包的構建 - SPEC 基礎知識


RPM 系列文章:

spec 文件

制作 rpm 軟件包並不是一件復雜的工作,其中的關鍵在於編寫軟件包的 spec 描述文件。

要想制作一個 rpm 軟件包就必須寫一個軟件包描述文件 spec。這個文件中包含了軟件包的諸多信息,如:軟件包的名字、版本、類別、說明摘要、創建時要執行什么指令、安裝時要執行什么操作、以及軟件包所要包含的文件列表等等。

實際過程中,最關鍵的地方,是要清楚虛擬路徑的位置,以及宏的定義。

文件頭

這個區域定義的 NameVersion 這些字段對應的值可以在后面通過 %{name},%{version} 這樣的方式來引用,類似於 C 語言中的宏

  • Summary:用一句話概括該軟件s包盡量多的信息。
  • Name:軟件包的名字,最終 rpm 軟件包是用該名字與版本號(Version)、釋出號(Release)及體系號來命名軟件包的,后面可使用 %{name} 的方式引用
  • Version:軟件版本號。僅當軟件包比以前有較大改變時才增加版本號,后面可使用%{version}引用
  • Release:軟件包釋出號/發行號。一般我們對該軟件包做了一些小的補丁的時候就應該把釋出號加 1,后面可使用 %{release} 引用
  • Packager:打包的人(一般喜歡寫個人郵箱)
  • Vendor:軟件開發者的名字,發行商或打包組織的信息,例如RedFlagCo,Ltd
  • License:軟件授權方式,通常是GPL(自由軟件)或GPLv2,BSD
  • Copyright:軟件包所采用的版權規則。具體有:GPL(自由軟件)BSDMITPublic Domain(公共域)Distributable(貢獻)commercial(商業)Share(共享)等,一般的開發都寫 GPL
  • Group:軟件包所屬類別
    • Development/System (開發/系統)
    • System Environment/Daemons (系統環境/守護)
  • Source:源程序軟件包的名字/源代碼包的名字,如 stardict-2.0.tar.gz。可以帶多個用 Source1Source2 等源,后面也可以用 %{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}

還有例如PreReqRequires(pre)Requires(post)Requires(preun)Requires(postun)BuildRequires等都是針對不同階段的依賴指定。

關於prepostpreunpostun含義理解,感覺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 目錄下執行源碼包的編譯。一般是執行執行常見的 configuremake 操作。

該階段一般由多個 make 命令組成。與 %prep 段落一樣,這些命令可以是 shell 命令,也可以是宏。

記住兩點:

  1. %build%install 的過程中,都必須把編譯和安裝的文件定義到“虛擬根目錄” 中
  2. %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 階段有兩個特性:

  1. %{buildroot} 里的所有文件都要明確被指定是否要被打包到 rpm里。什么意思呢?假如,%{buildroot} 目錄下有 4 個目錄 a、b、c和d,在 %files 里僅指定 a 和 b 要打包到 rpm 里,如果不把 c 和 d 用 exclude 聲明是要報錯的;
  2. 如果聲明了 %{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

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM