rpm包的制作及动态库问题

来源:互联网 发布:淘宝网健身护腕 编辑:程序博客网 时间:2024/04/30 11:13

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。


参考资料
http://www.linuxfly.org/post/137/