1. 程式人生 > >RPM打包原理、示例、詳解及備查( 轉)

RPM打包原理、示例、詳解及備查( 轉)

build line 6.4 介紹 很多 his 原理 動態鏈接 author

  RPM(Redhat Package Manager)是用於Redhat、CentOS、Fedora等Linux 分發版(distribution)的常見的軟件包管理器。因為它允許分發已編譯的軟件,所以用戶只用一個命令就可以安裝軟件。看到這篇文章的朋友想必已經知道RPM是個啥,rpm/yum命令怎麽用,廢話不多說,直接進入正題,來看看RPM包咋打。

1 準備

  首先請準備一個Linux環境,比如CentOS。RPM打包使用的是rpmbuild命令,這個命令來自rpm-build包,這個是必裝的。

yum install rpm-build

  當然也可以直接安裝rpmdevtools,這個工具還包含一些其他的工具,同時它依賴rpm-build,所以直接安裝的話會同時把rpm-build裝上。

yum install rpmdevtools

  當然,根據不同的軟件構建過程,還需要其他的編譯打包工具,比如C語言的makegcc,python的setuptools等,根據需要安裝即可。

2 原理

  RPM打包的時候需要編譯源碼,還需要把編譯好的配置文件啊二進制命令文件啊之類的東西按照安裝好的樣子放到合適的位置,還要根據需要對RPM的包進行測試,這些都需要先有一個“工作空間”。rpmbuild命令使用一套標準化的“工作空間”:

rpmdev-setuptree

  rpmdev-setuptree這個命令就是安裝rpmdevtools帶來的。可以看到運行了這個命令之後,在$HOME

家目錄下多了一個叫做rpmbuild的文件夾,裏邊內容如下:

$ tree rpmbuild
rpmbuild
├── BUILD
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS

如果沒有安裝rpmdevtools的話,其實用mkdir命令創建這些文件夾也是可以的。

mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}

  從這些文件的名字大體也能看得出來都是幹嘛用的。具體來說:

默認位置宏代碼名稱用途
~/rpmbuild/SPECS %_specdir Spec 文件目錄 保存 RPM 包配置(.spec)文件
~/rpmbuild/SOURCES %_sourcedir 源代碼目錄 保存源碼包(如 .tar 包)和所有 patch 補丁
~/rpmbuild/BUILD %_builddir 構建目錄 源碼包被解壓至此,並在該目錄的子目錄完成編譯
~/rpmbuild/BUILDROOT %_buildrootdir 最終安裝目錄 保存 %install 階段安裝的文件
~/rpmbuild/RPMS %_rpmdir 標準 RPM 包目錄 生成/保存二進制 RPM 包
~/rpmbuild/SRPMS %_srcrpmdir 源代碼 RPM 包目錄 生成/保存源碼 RPM 包(SRPM)

  SPECS下是RPM包的配置文件,是RPM打包的“圖紙”,這個文件會告訴rpmbuild命令如何去打包。“宏代碼”這一列就可以在SPEC文件中用來代指所對應的目錄,類似於編程語言中的宏或全局變量。當然~/rpmbuild這個文件夾也是有宏代碼的,叫做%_topdir

  打包的過程有點像是流水線,分好幾個工序:
  1. 首先,需要把源代碼放到%_sourcedir中;
  2. 然後,進行編譯,編譯的過程是在%_builddir中完成的,所以需要先把源代碼復制到這個目錄下邊,一般情況下,源代碼是壓縮包格式,那麽就解壓過來即可;
  3. 第三步,進行“安裝”,這裏有點類似於預先組裝軟件包,把軟件包應該包含的內容(比如二進制文件、配置文件、man文檔等)復制到%_buildrootdir中,並按照實際安裝後的目錄結構組裝,比如二進制命令可能會放在/usr/bin下,那麽就在%_buildrootdir下也按照同樣的目錄結構放置;
  4. 然後,需要配置一些必要的工作,比如在實際安裝前的準備啦,安裝後的清理啦,以及在卸載前後要做的工作啦等等,這樣也都是通過配置在SPEC文件中來告訴rpmbuild命令;
  5. 還有一步可選操作,那就是檢查軟件是否正常運行;
  6. 最後,生成的RPM包放置到%_rpmdir,源碼包放置到%_srpmdir下。

  以上這些步驟都是配置在SPEC文件中的,具體來說各個階段:

階段讀取的目錄寫入的目錄具體動作
%prep %_sourcedir %_builddir 讀取位於 %_sourcedir 目錄的源代碼和 patch 。之後,解壓源代碼至 %_builddir的子目錄並應用所有 patch。
%build %_builddir %_builddir 編譯位於 %_builddir 構建目錄下的文件。通過執行類似 ./configure && make的命令實現。
%install %_builddir %_buildrootdir 讀取位於 %_builddir 構建目錄下的文件並將其安裝至 %_buildrootdir 目錄。這些文件就是用戶安裝 RPM 後,最終得到的文件。註意一個奇怪的地方: 最終安裝目錄 不是 構建目錄。通過執行類似 make install 的命令實現。
%check %_builddir %_builddir 檢查軟件是否正常運行。通過執行類似 make test 的命令實現。很多軟件包都不需要此步。
bin %_buildrootdir %_rpmdir 讀取位於 %_buildrootdir 最終安裝目錄下的文件,以便最終在 %_rpmdir 目錄下創建 RPM 包。在該目錄下,不同架構的 RPM 包會分別保存至不同子目錄, noarch 目錄保存適用於所有架構的 RPM 包。這些 RPM 文件就是用戶最終安裝的 RPM 包。
src %_sourcedir %_srcrpmdir 創建源碼 RPM 包(簡稱 SRPM,以.src.rpm 作為後綴名),並保存至 %_srcrpmdir 目錄。SRPM 包通常用於審核和升級軟件包。

3 示例

  解釋再多不如一個例子來的明白,這裏用官方文檔中的例子來操作一遍。
  下面演示 GNU“Hello World” 項目的打包過程。雖然用 C 語言程序打印 “Hello World” 到標準輸出是小菜一碟,但 GNU 版本包含了與一個典型的 FOSS 軟件項目相關的最常用的外圍組件,包括配置/編譯/安裝環境、文檔、國際化等等。GNU 版本包含了一個由源代碼和 configure/make 腳本組成的 tar 文件,但並不包含打包信息。因此,這是一個很好的 RPM 包打包示例。

3.1 下載源碼

  還記得前面介紹到的幾個階段嗎,先準備源碼,這裏我們直接下載官方例子的源碼,是個壓縮包:

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

不知道為啥有時候源碼包下起來特別慢甚至下不動,可以先用迅雷下載下來,然後傳到虛擬機裏。

3.2 編輯SPEC文件

  然後後續的步驟就交給SPEC文件來配置了,編輯SPEC文件(Emacs 和 vi 的最新版本有 .spec 文件編輯模式,它會在創建新文件時打開一個類似的模板。所以可使用以下命令來自動使用模板文件):

cd ~/rpmbuild/SPECS
vim hello.spec

  既然有模板,那麽後邊的工作就是填空題了:

Name:     hello
Version:  2.1
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 項目所需的所有部分, 包括配置, 構建, 國際化, 幫助文件等.

%prep
%setup -q

%build
%configure
make %{?_smp_mflags}

%install
make install DESTDIR=%{buildroot}

%files
%doc

%changelog
* Sun Dec 4 2016 Your Name <[email protected]> - 2.10-1
- Update to 2.10
* Sat Dec 3 2016 Your Name <[email protected]> - 2.9-1
- Update to 2.9
  1. Name 標簽就是軟件名,Version 標簽為版本號,而 Release 是發布編號。
  2. Summary 標簽是簡要說明,英文的話第一個字母應大寫,以避免 rpmlint 工具(打包檢查工具)警告。
  3. License 標簽說明軟件包的協議版本,審查軟件的 License 狀態是打包者的職責,這可以通過檢查源碼或 LICENSE 文件,或與作者溝通來完成。
  4. Group 標簽過去用於按照 /usr/share/doc/rpm-/GROUPS 分類軟件包。目前該標記已丟棄,vim的模板還有這一條,刪掉即可,不過添加該標記也不會有任何影響。
    %changelog 標簽應包含每個 Release 所做的更改日誌,尤其應包含上遊的安全/漏洞補丁的說明。Changelog 日誌可使用 rpm --changelog -q <packagename> 查詢,通過查詢可得知已安裝的軟件是否包含指定漏洞和安全補丁。%changelog 條目應包含版本字符串,以避免 rpmlint 工具警告。
  5. 多行的部分,如 %changelog%description 由指令下一行開始,空行結束。
  6. 一些不需要的行 (如 BuildRequires 和 Requires) 可使用 ‘#’ 註釋。
  7. %prep%build%install%file暫時用默認的,未做任何修改。

3.3 構建RPM包

  有點迫不及待了,嘗試執行以下命令,以構建源碼、二進制和包含調試信息的軟件包:

rpmbuild -ba hello.spec

  1)包含要安裝的文件
  不過上邊的命令執行失敗了0_0。
  命令執行後,提示並列出未打包的文件:

RPM build errors:
    Installed (but unpackaged) file(s) found:
   /usr/bin/hello
   /usr/share/info/dir
   /usr/share/info/hello.info.gz
   /usr/share/locale/bg/LC_MESSAGES/hello.mo
   /usr/share/locale/ca/LC_MESSAGES/hello.mo
   ...

  那些需要安裝在系統中的文件,我們需要在 %files 中聲明它們,這樣rpmbuild命令才知道哪些文件是要安裝的。
  註意不要使用形如 /usr/bin/ 的硬編碼, 應使用類似 %{_bindir}/hello 這樣的宏來替代。手冊頁應在 %doc 中聲明 : %doc %{_mandir}/man1/hello.1.*
  由於示例的程序使用了翻譯和國際化,因此會看到很多未聲明的 i18 文件。 使用 推薦方法 來聲明它們:

  • 包含程序安裝的相關文件
  • 查找 %install 中的語言文件: %find_lang %{name}
  • 添加編譯依賴: BuildRequires: gettext
  • 聲明找到的文件: %files -f %{name}.lang

  這樣下來,%files部分的內容為:

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

  2)info文件的處理
  如果程序使用 GNU info 文件,你需要確保安裝和卸載軟件包,不影響系統中的其他軟件,按以下步驟操作:

  • 在 %install 中添加刪除 ‘dir’ 文件的命令: rm -f %{buildroot}/%{_infodir}/dir
  • 在安裝後和卸載前添加依賴 Requires(post): infoRequires(preun): info
  • 添加以下安裝腳本(在%install和%files中間即可,分別對應安裝後和卸載前的階段,詳見後邊內容):
%post
/sbin/install-info %{_infodir}/%{name}.info %{_infodir}/dir || :

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

  3)看看各個目錄裏邊的東西
  * %_sourcedir下邊仍然是源碼的壓縮包;
  * %_builddir下邊是源碼解壓出來的文件夾hello-2.10及其下邊的所有文件;
  * %_buildrootdir下邊是一個名為“hello-2.10-1.el7.centos.x86_64”的文件夾(那麽生成的RPM包的完整名稱也是{Name}-{Version}-{Release}.{Arch}.rpm),這個文件夾下邊有“usr”文件夾,其下還有“bin”、“lib”、“share”、“src”這幾個文件夾,可以看到這裏的目錄結構和安裝之後各個文件和文件夾的位置已經是基本一致的了。這裏要註意的是,“usr”所在的“根目錄”,也就是“hello-2.10-1.el7.centos.x86_64”這個文件夾,用宏表示就是%{buildroot},有的地方也用$RPM_BUILD_ROOT 代替 %{buildroot},不過跟%{_buildrootdir}不是一個概念,請註意。

  為什麽是“趁著失敗”呢,因為成功打包之後有些文件夾(比如%_builddir%_buildrootdir)內的內容就會被清理掉了,不過也可以在%build%install階段的時候把這倆文件夾內的東西tree一下或者幹脆復制到其他地方再看也行。
那麽%build%install以及其他幾個階段一般怎麽配置呢?

  4)本示例最終的完整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/hello
Source0:  http://ftp.gnu.org/gnu/hello/%{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
%setup -q

%build
%configure
make %{?_smp_mflags}

%install
make install DESTDIR=%{buildroot}
%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 Dec 4 2016 Your Name <[email protected]> - 2.10-1
- Update to 2.10
* Sat Dec 3 2016 Your Name <[email protected]> - 2.9-1
- Update to 2.9

  那麽就開動起來,在執行一下rpmbuild命令瞅瞅吧:

rpmbuild -ba hello.spec

  OK,執行成功了,看看成果吧:

$ tree ~/rpmbuild/*RPMS
/root/rpmbuild/RPMS
└── x86_64
    ├── hello-2.10-1.el7.centos.x86_64.rpm
    └── hello-debuginfo-2.10-1.el7.centos.x86_64.rpm
/root/rpmbuild/SRPMS
└── hello-2.10-1.el7.centos.src.rpm

  在RPMS文件夾下生成了RPM包,在x86_64下,表示所應用的架構,由於沒有指定arch為noarch,所以默認用本機架構。在SRPMS文件夾下生產了源碼包,源碼包當然木有架構這一說了。
  所以有些人喜歡在裝軟件的時候從源碼開始安裝,因為更能貼合本機的物理情況,就像用光盤安裝windows和GHOST安裝windows,相對來說光盤一步一步安裝更好一點點,不過我比較懶,還是直接yum install

  5)運行一下下
  既然已經有RPM包了,那就安裝上吧:

rpm -ivh ~/rpmbuild/RPMS/x86_64/hello-2.10-1.el7.centos.x86_64.rpm 

  運行一下:

$ hello
Hello, world!
$ which hello
/usr/bin/hello
$ rpm -qf `which hello`
hello-2.10-1.el7.centos.x86_64

  可以看到編譯好的二進制文件hello已經裝到/usr/bin下了,其他位置的文件請自行查看吧^_^。因為這個示例程序五臟俱全,不妨man一下,看看使用文檔~

man hello

4 詳解

  SPEC文件是RPM打包的核心,下面就對SPEC文件中漏掉的而且比較重要的關於各個部分的配置方法進行詳細說明:

4.1 %prep階段

  %prep 部分描述了解壓源碼包的方法。一般而言,其中包含 %autosetup 命令。另外,還可以使用 %setup%patch命令來指定操作 Source0Patch0 等標簽的文件。

  %autosetup 命令
  %autosetup 命令用於解壓源碼包。可用選項包括:

  • -n name : 如果源碼包解壓後的目錄名稱與 RPM 名稱不同,此選項用於指定正確的目錄名稱。例如,如果 tarball 解壓目錄為 FOO,則使用 “%autosetup -n FOO”。
  • -c name : 如果源碼包解壓後包含多個目錄,而不是單個目錄時,此選項可以創建名為 name 的目錄,並在其中解壓。

  %setup 命令
  如果使用 %setup 命令,通常使用 -q 抑止不必要的輸出。
  如果需要解壓多個文件,有更多 %spec 選項可用,這對於創建子包很有用。常用選項如下:

  • -a number:在切換目錄後,只解壓指定序號的 Source 文件(例如 “-a 0” 表示 Source0)。
  • -b number :在切換目錄前, 只解壓指定序號的 Source 文件(例如 “-b 0” 表示 Source0)。
  • -D:解壓前,不刪除目錄。
  • -T:禁止自動解壓歸檔。

  %patch 命令
  如果使用 %autosetup 命令,則不需要手動進行補丁管理。如果你的需求很復雜,或需要與 EPEL 兼容,需要用到此部分的內容。%patch0 命令用於應用 Patch0(%patch1 應用 Patch1,以此類推)。Patches 是修改源碼的最佳方式。常用的 -pNUMBER 選項,向 patch 程序傳遞參數,表示跳過 NUM 個路徑前綴。

  補丁文件名通常像這樣 telnet-0.17-env.patch,命名格式為 %{name} - %{version} - REASON.patch(有時省略 version 版本)。補丁文件通常是 diff -u 命令的輸出;如果你在 ~/rpmbuild/BUILD 子目錄執行此命令,則之後便不需要指定 -p 選項。

為一個文件制作補丁的步驟:

cp foo/bar foo/bar.orig
vim foo/bar
diff -u foo/bar.orig foo/bar > ~/rpmbuild/SOURCES/PKGNAME.REASON.patch

如果需要修改多個文件,簡單方法是復制 BUILD 下的整個子目錄,然後在子目錄執行 diff。切換至 ~rpmbuild/BUILD/NAME 目錄後,執行以下命令:

cp -pr ./ ../PACKAGENAME.orig/
... 執行修改 ...
diff -ur ../PACKAGENAME.orig . > ~/rpmbuild/SOURCES/NAME.REASON.patch

如果你想在一個補丁中編輯多個文件,你可以在編輯之前,使用 .orig 擴展名復制原始文件。然後,使用 gendiff(在 rpm-build 包中)創建補丁文件。

4.2 %build階段

  %build階段顧名思義就是對解壓到%_builddir下的源碼進行編譯的階段,整個過程在該目錄下完成。
  許多程序使用 GNU configure 進行配置。默認情況下,文件會安裝到前綴為 “/usr/local” 的路徑下,對於手動安裝很合理。然而,打包時需要修改前綴為 “/usr”。共享庫路徑視架構而定,安裝至 /usr/lib 或 /usr/lib64 目錄。
  由於 GNU configure 很常見,可使用 %configure 宏來自動設置正確選項(例如,設置前綴為 /usr)。一般用法如下:

 %configure
 make %{?_smp_mflags}

  若需要覆蓋 makefile 變量,請將變量作為參數傳遞給 make:

make %{?_smp_mflags} CFLAGS="%{optflags}" BINDIR=%{_bindir}

你會發現SPEC中會用到很多預定義好的宏,用來通過一個簡單的宏來完成一個或一系列常見的操作,比如:%prep階段用於解壓的%setup%autosetup%build階段的%configure等。

4.3 %install階段

  此階段包含安裝階段需要執行的命令,即從 %{_builddir} 復制相關文件到 %{buildroot} 目錄(通常表示從 ~/rpmbuild/BUILD 復制到 ~/rpmbuild/BUILDROOT/XXX) 目錄,並根據需要在 %{buildroot} 中創建必要目錄。

容易混淆的術語:
* “build 目錄”,也稱為 %{_builddir},實際上與 “build root”,又稱為 %{buildroot},是不同的目錄。在前者中進行編譯,並將需要打包的文件從前者復制到後者, %{buildroot}通常為 ~/rpmbuild/BUILD/%{name}-%{version}-%{release}.%{arch}
* 在 %build 階段,當前目錄為 %{buildsubdir},是 %prep 階段中在 %{_builddir} 下創建的子目錄。這些目錄通常名為 ~/rpmbuild/BUILD/%{name}-%{version}
* %install 階段的命令不會在用戶安裝 RPM 包時執行,此階段僅在打包時執行。

  一般,這裏執行 “make install” 之類的命令:

%install
rm -rf %{buildroot} # 僅用於 RHEL 5
%makeinstall
  • 理想情況下,對於支持的程序,你應該使用 %makeinstall(這又是一個宏),它等同於 DESTDIR=%{buildroot},它會將文件安裝到 %{buildroot} 目錄中。

    使用 “%makeinstall” 宏。此方法可能有效,但也可能失敗。該宏會展開為 make prefix=%{buildroot}%{_prefix} bindir=%{buildroot}%{_bindir} ... install,可能導致某些程序無法正常工作。請在 %{buildroot} 根據需要創建必要目錄。

  • 使用 auto-destdir 軟件包的話,需要 BuildRequires: auto-destdir,並將 make install 修改為 make-redir DESTDIR=%{buildroot} install。這僅適用於使用常用命令安裝文件的情況,例如 cp 和 install。

  • 手動執行安裝。這需要在 %{buildroot} 下創建必要目錄,並從 %{_builddir} 復制文件至 %{buildroot} 目錄。要特別註意更新,通常會包含新文件。示例如下:
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{_bindir}/
cp -p mycommand %{buildroot}%{_bindir}/

4.4 %check 階段

  如果需要執行測試,使用 %check 是個好主意。測試代碼應寫入 %check 部分(緊接在 %install 之後,因為需要測試 %{buildroot} 中的文件),而不是寫入 %{build} 部分,這樣才能在必要時忽略測試。通常,此部分包含:

make test

  有時候也可以用:

make check

  請熟悉 Makefile 的用法,並選擇適當的方式。

4.5 %files 部分

  此部分列出了需要被打包的文件和目錄。

  %files 基礎
  %defattr 用於設置默認文件權限,通常可以在 %files 的開頭看到它。註意,如果不需要修改權限,則不需要使用它。其格式為: 

%defattr(<文件權限>, <用戶>, <用戶組>, <目錄權限>)

  第 4 個參數通常會省略。常規用法為 %defattr(-,root,root,-),其中 “-” 表示默認權限。
  您應該列出該軟件包擁有的所有文件和目錄。盡量使用宏代替目錄名,具體的宏列表如下:

%{_sysconfdir}        /etc
%{_prefix}            /usr
%{_exec_prefix}       %{_prefix}
%{_bindir}            %{_exec_prefix}/bin
%{_libdir}            %{_exec_prefix}/%{_lib}
%{_libexecdir}        %{_exec_prefix}/libexec
%{_sbindir}           %{_exec_prefix}/sbin
%{_sharedstatedir}    /var/lib
%{_datarootdir}       %{_prefix}/share
%{_datadir}           %{_datarootdir}
%{_includedir}        %{_prefix}/include
%{_infodir}           /usr/share/info
%{_mandir}            /usr/share/man
%{_localstatedir}     /var
%{_initddir}          %{_sysconfdir}/rc.d/init.d
%{_var}               /var
%{_tmppath}           %{_var}/tmp
%{_usr}               /usr
%{_usrsrc}            %{_usr}/src
%{_lib}               lib (lib64 on 64bit multilib systems)
%{_docdir}            %{_datadir}/doc
%{buildroot}          %{_buildrootdir}/%{name}-%{version}-%{release}.%{_arch}
$RPM_BUILD_ROOT       %{buildroot}

  如果路徑以 “/” 開頭(或從宏擴展),則從 %{buildroot} 目錄取用。否則,假設文件在當前目錄中(例如:在 %{_builddir} 中,包含需要的文檔)。如果您的包僅安裝一個文件,如 /usr/sbin/mycommand,則 %files 部分如下所示:

%files
%{_sbindir}/mycommand

  若要使軟件包不受上遊改動的影響,可使用通配符匹配所有文件:

%{_bindir}/*

  包含一個目錄:

%{_datadir}/%{name}/

  註意,%{_bindir}/* 不會聲明此軟件包擁有 /usr/bin 目錄,而只包含其中的文件。如果您列出一個目錄,則該軟件包擁有這個目錄,及該目錄內的所有文件和子目錄。因此,不要列出 %{_bindir},並且要小心的處理那些可能和其他軟件包共享的目錄。

  如果存在以下情況,可能引發錯誤:

  • 通配符未匹配到任何文件或目錄
  • 文件或目錄被多次列出
  • 未列出 %{buildroot} 下的某個文件或目錄

  您也可以使用 %exclude 來排除文件。這對於使用通配符來列出全部文件時會很有用,註意如果未匹配到任何文件也會造成失敗。

  %files 前綴
  上邊的“hello”的示例中,%files部分還有用到%doc等宏,可能您看得一知半解,這裏詳細介紹一下。如果需要在 %files 部分添加一個或多個前綴,用空格分隔。

  %doc 用於列出 %{_builddir} 內,但不復制到 %{buildroot} 中的文檔。通常包括 READMEINSTALL等。它們會保存至 /usr/share/doc 下適當的目錄中,不需要聲明 /usr/share/doc 的所有權。

註意: 如果指定 %doc 條目,rpmbuild < 4.9.1 在安裝前會將 %doc 目錄刪除。這表明已保存至其中的文檔,例如,在 %install 中安裝的文檔會被刪除,因此最終不會出現在軟件包中。如果您想要在 %install 中安裝一些文檔,請將它們臨時安裝到 build 目錄(不是 build root 目錄)中,例如 _docs_staging,接著在 %files 中列出,如 %doc _docs_staging/* 這樣。

  配置文件保存在 /etc 中,一般會這樣指定(確保用戶的修改不會在更新時被覆蓋):

%config(noreplace) %{_sysconfdir}/foo.conf

  如果更新的配置文件無法與之前的配置兼容,則應這樣指定:

%config %{_sysconfdir}/foo.conf

  “%attr(mode, user, group)” 用於對文件進行更精細的權限控制,”-” 表示使用默認值:

%attr(0644, root, root) FOO.BAR

  “%caps(capabilities)” 用於為文件分配 POSIX capabilities。例如:

%caps(cap_net_admin=pe) FOO.BAR

  如果包含特定語言編寫的文件,請使用 %lang 來標註:

%lang(de) %{_datadir}/locale/de/LC_MESSAGES/tcsh*

  使用區域語言(Locale)文件的程序應遵循 處理 i18n 文件的建議方法:

  • 在 %install 步驟中找出文件名: %find_lang ${name}
  • 添加必要的編譯依賴: BuildRequires: gettext
  • 使用找到的文件名: %files -f ${name}.lang

4.6 Scriptlets

  當用戶安裝或卸載 RPM 時,您可能想要執行一些命令。這可以通過 scriptlets 完成。

  腳本片段可以:

  • 在軟體包安裝之前 (%pre) 或之後 (%post) 執行
  • 在軟體包卸載之前 (%preun) 或之後 (%postun) 執行
  • 在事務開始 (%pretrans) 或結束 (%posttrans) 時執行

例如,每個二進制 RPM 包都會在動態鏈接器的默認路徑中存儲共享庫文件,並在 %post 和 %postun 中調用 ldconfig 來更新庫緩存。如果軟件包有多個包含共享庫的子包,則每個軟體包也需要執行相同動作。

%post -p /sbin/ldconfig
%postun -p /sbin/ldconfig

如果僅執行一個命令,則 “-p” 選項會直接執行,而不啟用 shell。然而,若有許多命令時,不要使用此選項,按正常編寫 shell 腳本即可。

  如果你在腳本片段中執行任何程序,就必須以 Requires(CONTEXT)(例: Requires(post))的形式列出所有依賴。

  %pre%post%preun%postun 提供 $1 參數,表示動作完成後,系統中保留的此名稱的軟件包數量。因此可用於檢查軟件安裝情況,不過不要比較此參數值是否等於 2,而是比較是否大於等於 2。對於%pretrans%posttrans,$1 的值恒為 0。

  例如,如果軟件包安裝了一份 info 手冊,那麽可以用 info 包提供的 install-info 來更新 info 手冊索引。首先,我們不保證系統已安裝 info 軟件包,除非明確聲明需要它;其次,我們不想在 install-info 執行失敗時,使軟件包安裝失敗:

Requires(post): info
Requires(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

  上邊的示例中還有一個安裝 info 手冊時的小問題需要解釋一下。install-info 命令會更新 info 目錄,所以我們應該在 %install 階段刪除 %{buildroot} 中無用的空目錄:

rm -f %{buildroot}%{_infodir}/dir

5 命令及工具

5.1 rpmbuild打包

  一旦 SPEC 編寫完畢,請執行以下命令來構建 SRPM 和 RPM 包:

rpmbuild -ba program.spec

  如果成功,RPM 會保存至 ~/rpmbuild/RPMS,SRPM 會保存至 ~/rpmbuild/SRPMS

  如果失敗,請查看 BUILD 目錄的相應編譯日誌。為了幫助調試,可以用 --short-circuit 選項來忽略成功的階段。例如,若想要(略過更早的階段)重新從 %install 階段開始,請執行:

rpmbuild -bi --short-circuit program.spec

  如果只想創建 RPM,請執行:

rpmbuild -bb program.spec

  如果只想創建 SRPM(不需要執行 %prep 或 %build 或其他階段),請執行:

rpmbuild -bs program.spec

5.2 rpmlint檢查

  為避免常見錯誤,請先使用 rpmlint 查找 SPEC 文件的錯誤:

rpmlint program.spec

  如果返回錯誤/警告,使用 “-i” 選項查看更詳細的信息。

  也可以使用 rpmlint 測試已構建的 RPM 包,檢查 SPEC/RPM/SRPM 是否存在錯誤。你需要在發布軟件包之前,解決這些警告。此頁面 提供一些常見問題的解釋。如果你位於 SPEC 目錄中,請執行:

rpmlint NAME.spec ../RPMS/*/NAME*.rpm ../SRPMS/NAME*.rpm

  進入 ~/rpmbuild/RPMS 下的特定架構目錄中,您會發現有許多二進制 RPM 包。使用以下命令快速查看 RPM 包含的文件和權限:

rpmls *.rpm

5.3 rpm安裝

  如果看上去正常,以 root 身份安裝它們:

rpm -ivp package1.rpm package2.rpm package3.rpm ...

  以不同方式來測試程序,看看是否全部都正常工作。如果是 GUI 工具,請確認其是否出現在桌面菜單中,否則表示 .desktop 條目可能有錯。
  最後卸載軟件包:

rpm -e package1 package2 package3

6 備查

6.1 rpmbuild目錄

默認位置宏代碼名稱用途
~/rpmbuild/SPECS %_specdir Spec 文件目錄 保存 RPM 包配置(.spec)文件
~/rpmbuild/SOURCES %_sourcedir 源代碼目錄 保存源碼包(如 .tar 包)和所有 patch 補丁
~/rpmbuild/BUILD %_builddir 構建目錄 源碼包被解壓至此,並在該目錄的子目錄完成編譯
~/rpmbuild/BUILDROOT %_buildrootdir 最終安裝目錄 保存 %install 階段安裝的文件
~/rpmbuild/RPMS %_rpmdir 標準 RPM 包目錄 生成/保存二進制 RPM 包
~/rpmbuild/SRPMS %_srcrpmdir 源代碼 RPM 包目錄 生成/保存源碼 RPM 包(SRPM)


6.2 spec文件階段

階段讀取的目錄寫入的目錄具體動作
%prep %_sourcedir %_builddir 讀取位於 %_sourcedir 目錄的源代碼和 patch 。之後,解壓源代碼至 %_builddir 的子目錄並應用所有 patch。
%build %_builddir %_builddir 編譯位於 %_builddir 構建目錄下的文件。通過執行類似 ./configure && make 的命令實現。
%install %_builddir %_buildrootdir 讀取位於 %_builddir 構建目錄下的文件並將其安裝至 %_buildrootdir 目錄。這些文件就是用戶安裝 RPM 後,最終得到的文件。註意一個奇怪的地方: 最終安裝目錄 不是 構建目錄。通過執行類似 make install 的命令實現。
%check %_builddir %_builddir 檢查軟件是否正常運行。通過執行類似 make test 的命令實現。很多軟件包都不需要此步。
bin %_buildrootdir %_rpmdir 讀取位於 %_buildrootdir 最終安裝目錄下的文件,以便最終在 %_rpmdir 目錄下創建 RPM 包。在該目錄下,不同架構的 RPM 包會分別保存至不同子目錄, noarch 目錄保存適用於所有架構的 RPM 包。這些 RPM 文件就是用戶最終安裝的 RPM 包。
src %_sourcedir %_srcrpmdir 創建源碼 RPM 包(簡稱 SRPM,以.src.rpm 作為後綴名),並保存至 %_srcrpmdir 目錄。SRPM 包通常用於審核和升級軟件包。

6.3 代表路徑的宏列表

%{_sysconfdir}        /etc
%{_prefix}            /usr
%{_exec_prefix}       %{_prefix}
%{_bindir}            %{_exec_prefix}/bin
%{_libdir}            %{_exec_prefix}/%{_lib}
%{_libexecdir}        %{_exec_prefix}/libexec
%{_sbindir}           %{_exec_prefix}/sbin
%{_sharedstatedir}    /var/lib
%{_datarootdir}       %{_prefix}/share
%{_datadir}           %{_datarootdir}
%{_includedir}        %{_prefix}/include
%{_infodir}           /usr/share/info
%{_mandir}            /usr/share/man
%{_localstatedir}     /var
%{_initddir}          %{_sysconfdir}/rc.d/init.d
%{_var}               /var
%{_tmppath}           %{_var}/tmp
%{_usr}               /usr
%{_usrsrc}            %{_usr}/src
%{_lib}               lib (lib64 on 64bit multilib systems)
%{_docdir}            %{_datadir}/doc
%{buildroot}          %{_buildrootdir}/%{name}-%{version}-%{release}.%{_arch}
$RPM_BUILD_ROOT       %{buildroot}

6.4 參考文檔

  • Fedora Packaging Guidelines
  • How to create an RPM package
  • How to create a GNU Hello RPM package
  • Spec File Preamble

RPM打包原理、示例、詳解及備查( 轉)