1. 程式人生 > >rpm包的製作及動態庫問題

rpm包的製作及動態庫問題

rpm包的製作

概要

rpm:Redhat Package Manager,是一種二進位制的檔案,安裝(稱解壓可能更合適,只是它還包括一些資料庫的操作,判斷依賴關係等)後可以直接執行的軟體包,所以它們是平臺相關的,即與體系結構作業系統完全一一對應的軟體包。

本文件主要從以下幾個方面進行介紹:

l  rpm的簡單使用命令

l  rpm包的製作流程

l  無法找到動態庫分析

l  rpm包與yum的關係

1.rpm的簡單使用命令

安裝:rpm -ivh XXXXX.rpm //其中有一個--prefix引數可能(後面解釋)可以修改安裝路徑

檢視:rpm -q XXXX //注意這裡只有rpm包的名字而不包含字尾

檢視包資訊:rpm -qip XXXX[.rpm] //可以檢視已安裝包及未安裝的包資訊

檢視包中的檔案:rpm -qlp XXXX[.rpm] //檢視包中的檔案,而且可以知道包的安裝路徑

解除安裝:rpm -e XXXX

2.製作RPM包流程

製作RPM是通過rpmbuild這個工具來實現的,常用的使用方式如下:

rpmbuild -v -bb xxx.spec //-v表示輸出詳細資訊,-bb只打二進位制檔案,-ba打原始碼與二進位制檔案

spec檔案:定義該打包過程的具體規範,如打哪個包,打在哪,編譯過程

rpm包的常規制作需要這樣幾個資料夾:

mkdir -p~/rpmbuild/{BUILD,RPMS,SRPMS,SPECS,SOURCES} //rpmbuild的上級目錄不是必須的,但一般採用這種方式結構更加清晰

BUILD:該目錄是原始碼解壓后豐放的目錄,後面的configure,make,install就在這個解壓後的原始碼檔案內進行RPMS:該目錄用於儲存打包成功後的二進位制rpm包,一般在打包的過程中系統會根據體系結構再建立一層目錄如RPMS/x86_86

SRPMS:該目錄用於儲存生成的原始碼rpm包(其實跟tar包一樣)

SPECS:儲存spec檔案

SOURCES:儲存原始碼檔案XXX.tar.gz

其實還有一個BUILDROOT目錄,但在redhat上預設沒有,這個資料夾是用來作為打包過程中install的目錄,這些目錄都是通過相應的巨集來定義的如_builddir定義BUILD資料夾的目錄,可以通過rpm --showrc命令來檢視rpmbuild使用的巨集。或者直接使用rpm --eval %_bindir

來檢視單個的定義。

下面看一個事例:(打mysql的包)

%define debug_package %{nil}   //不打debug包

%define installpath /usr/local

Summary: MYSQL open source database

Name: tb-mysql-server   //要打的包的名字

Version: 5.1.48         //版本

Release: 78.el5               //這個主要是由後面的svn來修改

Source0: %{name}-%{version}.tar.gz     //原始碼包

License: GPL

Group: Applications/Databases

#Perfix: /usr/local    //定義了該標籤的話,該包就是可relocate,即在安裝rpm包的時候可以指定安裝路徑(這可能會導致動態庫查詢的問題),否則的話會安裝到configure的prefix所指定的目錄中

AutoReqProv: no        //自動依賴包,及提供包的檢測,這裡取消是因為有些依賴包只要不是通過rpm安裝的(實際已安裝),對以rpm來說就是沒安裝,所以會導致該安裝包的安裝失敗,亦可以在安裝時使用--nodeps來強制取消這些依賴來安裝

Provides: mysql,mysql-server,mysqld  

BuildRoot: %{_topdir}/BUILD/%{name}-root   //定義rpm製作過程中,install的目錄

%description

The MySQL(TM) software delivers a very fast,multi-threaded, multi-user,

and robust SQL (Structured Query Language) databaseserver. MySQL Server

is intended for mission-critical, heavy-loadproduction systems as well

as for embedding into mass-deployed software.

%prep

%setup -q  //把原始碼包解壓到BUILD目錄下

%build

CFLAGS="-O3 -g" CXX=gccCXXFLAGS="-O3 -g -felide-constructors \

-fno-exceptions -fno-rtti" LDFLAGS="-R%installpath/lib/mysql"./configure --prefix=%installpath \

--with-extra-charsets=all \

--with-plugins=partition,heap,innobase,innodb_plugin,myisam,myisammrg,csv--enable-assembler

make -j 8

%install

rm -rf $RPM_BUILD_ROOT   //當定義了BuildRoot這個欄位的時候,RPM_BUILD_ROOT就是對應著該變數

make DESTDIR=$RPM_BUILD_ROOT install  //DESTDIR指定打包時的安裝步驟,檔案被安裝到哪裡,這裡並不是軟體包最後在電腦上rpm -ivh安裝的路徑。這裡也可以使用--prefix選項,兩種方式是不一樣的,使用DESTDIR時系統會自己在後面再加上configure指定的--prefix(這個才是rpm包最終安裝到我們電腦上的路徑,如果沒有指定的話configure一般有預設值為/usr/local),而make install 的--prefix的是一個絕對值,使用它的使用必須使用全部路徑而不是像DESTDIR,即必須是prefix=DESTDIR/usr/local(/usr/local是configure的prefix選項)

install -D -m644 support-files/my-medium.cnf$RPM_BUILD_ROOT/%installpath/share/mysql/my.cnf //這裡將常用的配製檔案拷貝到安裝包內

%post          //安裝後執行的指令碼

#/sbin/ldconfig

%postun            //解除安裝後執行的指令碼

#/sbin/ldconfig                  

%clean

#rm -rf $RPM_BUILD_ROOT   //清除打包時的安裝目錄

%files

%defattr(-,root,root)  //指定要打包的檔案的預設屬性,-表示系統預設值,分別表示mode,所屬使用者,組

%installpath  //指定哪些install後的檔案,打到rpm包中,這裡的路徑匹配會先加上$RPM_BUILD_ROOT,

然後再一級一級匹配,找出相應的檔案

注意:rpm打包過程是跟它的安裝過程完全沒有關係的,所以這裡指定的所有路徑都只是在打包過程中的,(除了configure的--prefix選項),如果在make install的時候沒有指定DESTDIR=$RPM_BUILD_ROOT,那打包時的install路徑就是configure的--prefix路徑,這種情況一般不是我們希望的,所以最後重新指定。files階段中還有很多的標識,如定義檔案為config,dir不同的標籤,它們的行為可能不一樣。使用rpmbuild,原始碼包的檔名必須嚴格按照name-version.tar.gz的形式,並且解壓後的資料夾必須是name-version,因為rpmbild會使用這些資訊去進入相應的目錄(應該可以通過設定哪些環境變數來修改,暫時沒找到)。

3.動態庫無法找到的原因

在安裝mysql的rpm包時,啟動client的時候,提示無法找到動態庫檔案libmysqlclient.so檔案。遇到這個問題最簡單的解決辦法就是在啟動前export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/**/lib/mysql/   (該檔案所在的目錄)

更好的方法也許是使用指令碼來封裝一下,但如果本來的啟動檔案就是一個指令碼的話,那麼我們似乎還要去修改這個指令碼檔案,這樣的操作是比較麻煩的。(該變數為session變數,所以該session結束後,該變數就被清除了,相應它也就比較安全)

第二種解決辦法,程式在查詢庫檔案的時候除了從LD_LIBRARY_PATH指定的路徑從,還會從系統目錄下/usr/lib,/usr/lib64這些目錄裡去找(預設是不去查詢/usr/local/lib目錄下的檔案),所以經常我們yum安裝庫檔案的時候都是安裝在/usr/lib的系統目錄下,而這種方法可行的根本原因在於/sbin/ldconf,這工具會把/etc/ld.so.conf檔案內所指定的目錄下的所有動態庫檔案載入到ld.so.cache系統快取檔案中,這個檔案內容大概是k-v的形式,key是為這個庫檔案指定一個新的名字,而value就是這個檔案的位置,所以我們可以簡單的猜測,當我們在使用yum或rpm安裝某個動態庫檔案是的時候,它們會在%post階段,執行一次ldconfig。這樣就可以把新增加的庫檔案載入到ld.so.cache。所以程式執行的時候查詢動態庫的過程:首先查詢環境變數LD_LIBRARY_PATH的目錄,如果沒有再查詢ld.so.cache,如果都沒找到就會報錯。

也就是在報這種錯誤的時候我們可以先檢視一下這個庫檔案是否存在,路徑是什麼,然後檢視LD_LIBRARY_PATH是否有這個路徑,如果沒有的話,那麼再通過ldcofig -p | grep XXX,如果兩個都沒有,那麼再執行一下ldconfig-v檢視ldconfig總共載入了哪些目錄,是否該庫檔案的目錄在其中。

所以第二種解決的辦法就是把該目錄寫到ld.so.conf檔案中,然後再執行一次ldconfig;

注意:執行ldconfg /path可以直接把該路徑載入到ld.so.cache檔案中,但是這不是持久的,當某個人再執行一次ldconfig的話,那麼這個資訊就又沒了,因為它沒有被寫到ld.so.conf檔案中。

現在就有一個問題,那麼我們在通過原始碼安裝庫檔案到自己指定的目錄,是否也是要進行上面的寫ld.so.conf+ldconfig的操作呢?下面以mysql的安裝為例描述一下。當我在通過原始碼直接安裝mysql之後,我發現ld.so.conf檔案並沒有寫上它的庫檔案的目錄~/mymysql/lib/mysql(當然ldconfig -p也沒找到),而此時的LD_LIBRARY_PATH也為空,但是它卻可以正常執行(注:只有mysql,mysqladmin會使用這些庫檔案,mysqld不會去使用[這兩個都可以通過在configure的時候指定是否使用動態庫,我們這裡假設mysqld使用靜態,mysql使用動態,這也是預設配置]),很是奇怪,為什麼上面兩種方式都沒能找到這個檔案,但卻可以正常執行(而當我安裝我的打包的二進位制安裝包時,在安裝的時候也自己指定了安裝一個目錄,卻無法執行mysql,libmysqlclien.so:no found~~~)。最後通過檢視mysql的Makefile檔案發現了一個-rpath的編譯選項,通過網查詢知道了它的作用:在編譯的連線過程時把動態庫的路徑直接寫死到目標程式中,因為在連線操作的時候必須也要指定庫檔案的位置(-L/XXXX/lib,這個只是指連線時查詢,而不能對執行時查詢有效),而當我們在使用原始碼安裝時configure就指定了程式安裝在哪裡,這個是一個絕對的路徑,所以它可以被寫進最終的可執行檔案中。該選項又可以通過LDFLAGS="-R/xxx/lib"來指定。

這就是為什麼原始碼包可以安裝到指定的位置,而二進位制包不行。當然我們也可以通過寫ld.so.conf的方法來實現:

%post

target=`rpm -ql rel_mysql | head -n 1`

CONFFILE=/etc/ld.so.conf

  if ! grep-q $target/lib/mysql $CONFFILE; then

    # append

    echo$target/lib/mysql >> $CONFFILE

  fi

/sbin/ldconfig

%postun

/sbin/ldconfig

在包安裝成功後執行向ld.so.conf檔案寫lib目錄,這裡我們在包解除安裝時並沒有清除該行目錄,因為只要該目錄的檔案被刪除了,它也不會再載入到ld.so.cache中。該方法有利有弊,比如當我們在編譯mysql-proxy的時候,需要一個高版本的GLib庫,而這個基礎庫又不敢隨便升級,雖然它們的版本不一樣,但一般安裝了一個庫檔案的時候,都會執行一個ln -s操作,把版本資訊去掉,只提供一個相對的主要版本資訊,如2.12與2.86,最後都會被ln為一個2.0的連線檔案,而ldconfig最後就是使用這個2.0的名字去建立k-v,也就是ldconfig cache中就會存在兩個一樣的k,那麼最終會使用哪一個?(還沒找到答案),是否會是發現第一個的真正版本無法滿足時,再查詢後面的,如果是這樣的話,那麼就可以滿足我們對高版本的要求,但是原來使用低版本的軟體在找到高版本時不會不成功,所以可能直接使用高版本的

庫,而這可能是不相容的。

所以也許對於基礎庫除非確定要更新,把舊的刪掉,使用新的,不然最好不要為了使讓某個軟體得到支援而更新基礎庫,這時使用-rpath可能是更好的選擇。

綜上我們可以總結出程式在執行的時候是可以通過三種方式去查詢動態庫的,第一種是由編譯時的-rpath指定(不能使用rpm包的relocate功能);第二種是LD_LIBRARY_PATH(該方法必須每次執行時都指定,可用指令碼封裝);第三種把它們安裝到/usr目錄下再執行ldconfig,或者安裝到其它目錄,再把目錄寫進ld.so.conf檔案,最後再ldconfig。

現在的軟體如果是使用到自己的庫檔案的時候一般都是使用-rpath的方式,而對於rpm包的方式時一般都是不支援relocate(其原因也是動態庫可能帶來的問題的原因)。

注:編譯時也要查詢動態庫檔案,這個與執行時查詢是完全沒有關係的,要讓編譯通過就必須在使用-I,-L來指定,而執行時的查詢如果預設方式沒有找到還是得自己指定的。它跟編譯完全沒有關係。

4.條件依賴
%if %{erl} == 0
BuildRequires: cmake, boost-devel = 1.33.1

%else
BuildRequires: cmake, boost-devel = 1.41.0
%endif

然後在rpmbuild --define "_topdir ${HOME}/rpmbuild" -D "erl $erl" 通過-D定義該變數

5.rpm與yum的關係

yum只是用來解決rpm包的依賴,僅此而已,它會把rpm依賴的包自動進行下載安裝,而且yum是絕對unrelocate。