常見的Linux發行版主要可以分為兩類,類ReadHat系列和類Debian系列,這里我們是以其軟件包的格式來划分的,這兩類系統分別提供了自己的軟件包管理系統和相應的工具。類RedHat系統中軟件包的后綴是rpm;類Debian系統中軟件包的后綴是deb。另一方面,類RedHat系統提供了同名的rpm命令來安裝、卸載、升級rpm軟件包;類Debian系統同樣提供了dpkg命令來對后綴是deb的軟件包進行安裝、卸載和升級等操作。
rpm的全稱是Redhat Package Manager,常見的使用rpm軟件包的系統主要有Fedora、CentOS、openSUSE、SUSE企業版、PCLinuxOS以及Mandriva Linux、Mageia等。使用deb軟件包后綴的類Debian系統最常見的有Debian、Ubuntu、Finnix等。
無論是rpm命令還是dpkg命令在安裝軟件包時都存在一個讓人非常頭疼的問題,那就是軟件包的依賴關系。這一點很多人應該深有體會,這也使初學者在接觸Linux系統時覺得很不方便的地方。慶幸的是,很多發行版都考慮到了這問題,於是Fedora和CentOS提供了yum來自動解決軟件包的安裝依賴,同樣的openSUSE提供了zypper,類Debian系統提供了apt-*命令。也就是說這些工具本質上最終還是調用了rpm(或者dpkg)是不過安裝前自動幫用戶解決了軟件包的安裝依賴。如下表所示:
分類 | 發行版 | 手動安裝命令 | 自動安裝命令 | 軟件包后綴 |
類RedHat |
Fedora/CentOS | rpm |
yum | *.rpm |
openSUSE/SUSE | zypper | |||
Mandriva Linux/Mageia | urpmi | |||
類Debian | Debian/Ubuntu | dpkg | apt-get | *.deb |
簡單點了說,如果你會在Fedora或者CentOS上用yum來自動安裝軟件包,那么在Debian或者Ubuntu上你就會用apt-get自動安裝軟件,同理,在openSUSE上你就會用zypper自動安裝軟件包。
本文檔主要描述如何通過軟件包的源代碼構建自己的rpm軟件安裝包。
從軟件運行的結構來說,一個軟件主要可以分為三個部分:可執行程序、配置文件和動態庫。當然還有可能會有相關文檔、手冊、供二次開發用的頭文件以及一些示例程序等等。其他部分都是可選的,只有可執行文件是必須的。
關於如何制作rpm軟件包的方法,網上教程也一大堆,談及最多的當屬rpmbuild這個命令行工具。這也是本文要介紹的“配角”,而主角是它的輸入對象,也就是所謂的SPEC文件的格式,寫法和說明。
rpm的打包流程相比deb的要稍微麻煩一些,因為它對打包目錄有一些嚴格的層次上的要求。如果你的rpm的版本<=4.4.x,那么rpmbuid工具其默認的工作路徑是/usr/src/redhat,這就使得普通用戶不能制作rpm包,因為權限的問題,在制作rpm軟件包時必須切換到root身份才可以。所以,rpm從4.5.x版本開始,將rpmbuid的默認工作路徑移動到用戶家目錄下的rpmbuild目錄里,即$HOME/rpmbuild,並且推薦用戶在制作rpm軟件包時盡量不要以root身份進行操作。
關於rpmbuild默認工作路徑的確定,通常由在/usr/lib/rpm/macros這個文件里的一個叫做%_topdir的宏變量來定義。如果用戶想更改這個目錄名,rpm官方並不推薦直接更改這個目錄,而是在用戶家目錄下建立一個名為.rpmmacros的隱藏文件(注意前面的點不能少,這是Linux下隱藏文件的常識),然后在里面重新定義%_topdir,指向一個新的目錄名。這樣就可以滿足某些“高級”用戶的差異化需求了。通常情況下.rpmmacros文件里一般只有一行內容,比如:
點擊(此處)折疊或打開
- %_topdir $HOME/myrpmbuildenv
在%_topdir目錄下一般需要建立6個目錄:
目錄名 | 說明 | macros中的宏名 |
BUILD | 編譯rpm包的臨時目錄 | %_builddir |
BUILDROOT | 編譯后生成的軟件臨時安裝目錄 | %_buildrootdir |
RPMS | 最終生成的可安裝rpm包的所在目錄 | %_rpmdir |
SOURCES | 所有源代碼和補丁文件的存放目錄 | %_sourcedir |
SPECS | 存放SPEC文件的目錄(重要) | %_specdir |
SRPMS | 軟件最終的rpm源碼格式存放路徑(暫時忽略掉,別掛在心上) | %_srcrpmdir |
小技巧:執行 rpmdev-setuptree 會在當前用戶家目錄下的rpmbuild目錄(如果該目錄不存在也會被自動創建)里自動建立上述目錄。
當上述目錄建立好之后,將所有用於生成 rpm 包的源代碼、 shell 腳本、配置文件都拷貝到 SOURCES 目錄里,注意 通常 情況下源碼的壓縮格式都為 *.tar.gz 格式 ( 當然還可以為其他格式,但那就是另外一種方式,這里先不介紹 ) 。然后,將最最最重要的 SPEC 文件,命名格式一般是“軟件名 - 版本 .spec ”的形式,將其拷貝到 SPECS 目錄下,切換到該目錄下執行:
點擊(此處)折疊或打開
- rpmbuild -bb 軟件名-版本.spec
最終我們想要的 rpm 軟件包就安安穩穩地躺在 RPMS 目錄下了。對,就這么簡單。
這里的關鍵就是上面的 SPEC 文件的寫法,我們可以用 rpmdev-newspec -o Name-version.spec 命令來生成 SPEC 文件的模板,然后在上面修改就可。例如:
點擊(此處)折疊或打開
- [root@localhost ~]# rpmdev-newspec -o myapp-0.1.0.spec
- Skeleton specfile (minimal) has been created to "myapp-0.1.0.spec".
- [root@localhost ~]# cat myapp-0.1.0.spec
- Name: myapp-0.1.0
- Version:
- Release: 1%{?dist}
- Summary:
-
- Group:
- License:
- URL:
- Source0:
- BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-
- BuildRequires:
- Requires:
-
- %description
-
- %prep
- %setup -q
-
- %build
- %configure
- make %{?_smp_mflags}
-
- %install
- rm -rf $RPM_BUILD_ROOT
- make install DESTDIR=$RPM_BUILD_ROOT
-
- %clean
- rm -rf $RPM_BUILD_ROOT
-
- %files
- %defattr(-,root,root,-)
- %doc
-
- %changelog
其實SPEC文件的核心是它定義了一些“階段”(%prep、%build、%install和%clean),當rpmbuild執行時它首先會去解析SPEC文件,然后依次執行每個“階段”里的指令。
接下來,我們來簡單了解一下SPEC文件的頭部。假如,我們的源碼包名字是myapp-0.1.0.tar.gz,那么myapp-0.1.0.spec的頭部一般如下的樣子:
點擊(此處)折疊或打開
- Name: myapp <===軟件包的名字(后面會用到)
- Version: 0.1.0 <===軟件包的版本(后面會用到)
- Release: 1%{?dist} <===發布序號
- Summary: my first rpm <===軟件包的摘要信息
- Group: <===軟件包的安裝分類,參見/usr/share/doc/rpm-4.x.x/GROUPS這個文件
- License: GPL <===軟件的授權方式
- URL: <===這里本來寫源碼包的下載路徑或者自己的博客地址或者公司網址之類
- Source0: %{name}-%{version}.tar.gz <===源代碼包的名稱(默認時rpmbuid回到SOURCES目錄中去找),這里的name和version就是前兩行定義的值。如果有其他配置或腳本則依次用Source1、Source2等等往后增加即可。
- BuildRoot: %{_topdir}/BUILDROOT <=== 這是make install時使用的“虛擬”根目錄,最終制作rpm安裝包的文件就來自這里。
- BuildRequires: <=== 在本機編譯rpm包時需要的輔助工具,以逗號分隔。假如,要求編譯myapp時,gcc的版本至少為4.4.2,則可以寫成gcc >=4.2.2。還有其他依賴的話則以逗號分別繼續寫道后面。
- Requires: <=== 編譯好的rpm軟件在其他機器上安裝時,需要依賴的其他軟件包,也以逗號分隔,有版本需求的可以
- %description <=== 軟件包的詳細說明信息,但最多只能有80個英文字符。
下面我們來看一下制作rpm包的幾個關鍵階段,以及所發生的事情:
階段 | 動作 |
%prep | 將%_sourcedir目錄下的源代碼解壓到%_builddir目錄下。如果有補丁的需要在這個階段進行打補丁的操作 |
%build | 在%_builddir目錄下執行源碼包的編譯。一般是執行./configure和make指令 |
%install | 將需要打包到rpm軟件包里的文件從%_builddir下拷貝%_buildrootdir目錄下。當用戶最終用rpm -ivh name-version.rpm安裝軟件包時,這些文件會安裝到用戶系統中相應的目錄里 |
制作rpm包 | 這個階段是自動完成的,所以在SPEC文件里面是看不到的,這個階段會將%_buildroot目錄的相關文件制作成rpm軟件包最終放到%_rpmdir目錄里 |
%clean | 編譯后的清理工作,這里可以執行make clean以及清空%_buildroot目錄等 |
每個階段的詳細說明如下:
- %prep階段
點擊(此處)折疊或打開
- %setup -q
當然,這句指令可以成功執行的前提是你位於SOURCES目錄下的源碼包必須是name-version.tar.gz的格式才行,它還會完成后續階段目錄的切換和設置。如果在這個階段你不用這條指令,那么后面每個階段都要自己手動去改變相應的目錄。解壓完成之后如果有補丁文件,也在這里做。想了解的童鞋可以自己去查查如何實現,也不難,這里我就不展開了。
- %build階段
點擊(此處)折疊或打開
- %configure
- make %{?_smp_mflags}
它就自動將軟件安裝時的路徑自動設置成如下約定:
- 可執行程序/usr/bin
- 依賴的動態庫/usr/lib或者/usr/lib64視操作系統版本而定。
- 二次開發的頭文件/usr/include
- 文檔及手冊/usr/share/man
注意,這里的%configure是個宏常量,會自動將prefix設置成/usr。另外,這個宏還可以接受額外的參數,如果某些軟件有某些高級特性需要開啟,可以通過給%configure宏傳參數來開啟。如果不用 %configure這個宏的話,就需要完全手動指定configure時的配置參數了。同樣地,我們也可以給make傳遞額外的參數,例如:
點擊(此處)折疊或打開
- make %{?_smp_mflags} CFLAGS="" …
- %install階段
這個階段就是執行make install操作。這個階段會在%_buildrootdir目錄里建好目錄結構,然后將需要打包到rpm軟件包里的文件從%_builddir里拷貝到%_buildrootdir里對應的目錄里。這個階段最常見的兩條指令是:
點擊(此處)折疊或打開
- rm -rf $RPM_BUILD_ROOT
- make install DESTDIR=$RPM_BUILD_ROOT
其中$RPM_BUILD_ROOT也可以換成我們前面定義的BuildRoot變量,不過要寫成%{buildroot}才可以,必須全部用小寫,不然要報錯。
如果軟件有配置文件或者額外的啟動腳本之類,就要手動用copy命令或者install命令你給將它也拷貝到%{buildroot}相應的目錄里。用copy命令時如果目錄不存在要手動建立,不然也會報錯,所以推薦用install命令。
- %clean階段
編譯完成后一些清理工作,主要包括對%{buildroot}目錄的清空(當然這不是必須的),通常執行諸如make clean之類的命令。
- 制作rpm軟件包的階段
點擊(此處)折疊或打開
- %files
- %defattr(-,root,root,-)
- %doc
在%files階段的第一條命令的語法是:
點擊(此處)折疊或打開
- %defattr(文件權限,用戶名,組名,目錄權限)
如果不牽扯到文件、目錄權限的改變則一般用%defattr(-,root,root,-)這條指令來為其設置缺省權限。所有需要打包到rpm包的文件和目錄都在這個地方列出,例如:
點擊(此處)折疊或打開
- %files
- %{_bindir}/*
- %{_libdir}/*
- %config(noreplace) %{_sysconfdir}/*.conf
在安裝rpm時,會將可執行的二進制文件放在/usr/bin目錄下,動態庫放在/usr/lib或者/usr/lib64目錄下,配置文件放在/etc目錄下,並且多次安裝時新的配置文件不會覆蓋以前已經存在的同名配置文件。
這里在寫要打包的文件列表時,既可以以宏常量開頭,也可以為“/”開頭,沒任何本質的區別,都表示從%{buildroot}中拷貝文件到最終的rpm包里;如果是相對路徑,則表示要拷貝的文件位於%{_builddir}目錄,這主要適用於那些在%install階段沒有被拷貝到%{buildroot}目錄里的文件,最常見的就是諸如README、LICENSE之類的文件。如果不想將%{buildroot}里的某些文件或目錄打包到rpm里,則用:
點擊(此處)折疊或打開
- %exclude dic_name或者file_name
但是關於%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/目錄里。
- %changelog階段
點擊(此處)折疊或打開
- * date +"%a %b %d %Y" 修改人 郵箱 本次版本x.y.z-p
- - 本次變更修改了那些內容
說了這么多,我們實戰一下。網上很多教程都是拿Tomcat或者Nigix開頭,這里我就先從簡單的mp3解碼庫libmad入手,將它打成一個rpm包,具體步驟如下:
(如果自己系統上沒有rpmbuild命令就安裝之:yum install rpm* rpm-build rpmdev*)

3、在rpmbuild/SPECS目錄下執行rpmdev-newspec -o libmad-0.15.1b.spec,會在當前目錄下生成名為libmad-0.15.1b.spec的模板文件。
4、將libmad-0.15.1b.spec修改成如下的樣子:
5、在rpmbuild/SPECS目錄下執行打包編譯:
點擊(此處)折疊或打開
- rpmbuild -bb libmad-0.15.1b.spec

因為我是64位系統,所以編譯出的libmad適用於CentOS6.0-64。
如果我們將libmad的源碼和spec文件拷貝32位系統上,再執行rpm打包,看看結果:

后記:
關於SPEC文件,還有一些其他特性,諸如安裝軟件包之前、之后要做的事情,以及卸載軟件包之前之后要做的事情,包括給源碼打補丁,關於這些特性感興趣的童鞋自己去摸索吧。最后給出一個完整的,包含了打補丁、安裝、卸載特性的SPEC文件模板:
點擊(此處)折疊或打開
- Name: test
- Version:
- Requires:
- %description
- …
- #==================================SPEC頭部====================================
- %prep
- %setup -q
- %patch <==== 在這里打包
- %build
- %configure
- make %{?_smp_mflags}
- %install
- rm -rf $RPM_BUILD_ROOT
- make install DESTDIR=$RPM_BUILD_ROOT
- %clean
- rm -rf $RPM_BUILD_ROOT
- %files
- %defattr(-,root,root,-)
- 要打包到rpm包里的文件清單
- %doc
- %changelog
- #==================================SPEC主體====================================
- %pre
- 安裝或者升級軟件前要做的事情,比如停止服務、備份相關文件等都在這里做。
- %post
- 安裝或者升級完成后要做的事情,比如執行ldconfig重構動態庫緩存、啟動服務等。
- %preun
- 卸載軟件前要做的事情,比如停止相關服務、關閉進程等。
- %postun
- 卸載軟件之后要做的事情,比如刪除備份、配置文件等。