autotools的用法

对make的思考:
1. make被设计为一个重编译的工具:
make这个工具最大的缺点就是:Dealing with dependencies。就是说user需要手动写生成各种目标的依赖关系。另外的缺点,就是任何一个安装用户,都得去改Makefile,以决定编译参数,安装路径等。这就是早期,在只有make工具的时候,人们所面临的不爽。事实上,make只是设计为一个方便重编译的工具,而编译什么,编译器怎么选择,编译参数等烦琐的事情完全交由用户写定。(后期的make,对Dealing with dependencies加入了对语言的理解,而有所改善)

**************************************************************************
autotools的思考:
(下面所有没有详细说明的请参看所附文档或网络文档)
1. autotools命名习惯:
ac后缀文件是autoconf的输入文件;
am后缀文件是automake的输入文件;
in后缀文件是configure的输入文件;
以后遇到一些生成的文件,按这个来理解。

2. configure.ac和Makefile.am是需要自己手写的,之后就是所谓的自动化流程:
现在把能够理解的重要的部分写在下面:
(1)configure.ac是由configure.scan作为模板,自己添加和修改它给你预留的东西而成的。
通过在比源文件高的目录下执行autoscan,生成一个configure.scan文件,这是给你的参考模板。
这个文件里就是一条条的m4宏,这些宏针对系统有的编译器是什么,是否有所需的库存在,是否有所需的头文件存在,是否支持一些语言特征(如const,inline)等等来检查。
这些检查是例行检查,是自动生成的(我们值得修改和注意的是AC_INIT,AC_CONFIG_SRCDIR和AC_CONFIG_HEADER三个宏)。
我们之后需要做的,就是将这个便宜货改名为configure.ac,并加入自己想检查的东西,所以我们需要知道一些典型的m4宏。BTW,m4的宏的实现就是Unix系统上的基本命令。
下面举例说明自己可能会加的东西,以及这些东西怎么影响Makefile和源文件:
(1.1)使用automake:
如果你想编译一堆文件,你可以自己写Makefile文件,执行make即可。但是为了方便,你可以只写一个Makefile.am文件,这个文件里交待清楚本Makefile所在目录下的文件有哪些,最后的目标文件名取名为哪些,以及进一步处理的子目录等(当然你在子目录下继续写Makefile.am文件)。你不需要写明文件依赖,编译器,编译参数,链接参数等(当然它允许你直接在这里写明)。之后就是用automake在最上层的Makefile.am目录,执行一下,结果会在所有有Makefile.am的目录里生成Makefile.in文件,作为以后configure文件的输入,它会结合探测的信息生成正式的Makefile在每个有Makefile.in的目录下。
我估计是因为格式问题,如果你想让configure文件使用automake生成的Makefile.in,你得先在configure.ac中加上两条m4宏:
AM_INIT_AUTOMAKE: 可能是起用解析吧(我不知)
AC_CONFIG_FILES: 把你在上面的目录和子目录下的所有的Makefile路径写进来,这个宏用做将那些Makefile.in改为正式的Makefile。

这里要说明的是,为什么都有Makefile.in了,还需要加入configure.ac,让之后生成的configure脚本来做Makefile.in到Makefile的转化呢? 那是因为,Makefile需要靠autoconf来提供灵活性,以适应不同的用户需求和系统平台:
configure脚本(就是来源于你写的configure.ac)用于接收用户指定编译选项(比如debug,比如选定的功能,比如安装路径),以及探测系统是否满足编译需求,最后的Makefile可以按照这两个方面而生成(典型的,比如你要configure.ac找一个SDL_image库,问它需要包含什么样的“-lxx”形式,它找到了,你比如会赋值一个变量,这个变量你如果写在Makefile.am里面了,就可以使得Makefile按照探测的结果而变化)。当然,你如果只利用automake的价值,不管configure那套东西,也是可以的,那么就得写死一些东西,就这么个道理。
(1.2)SDL库的侦测:
比如你想侦测SDL库,就得在configure.ac中写,有两种侦测方式,一种是利用SDL提供的m4宏,这前提是安装了SDL,那么用那些宏,你可以得到,为了编译进某个功能,你需要加入的编译和链接选项。
一种是用autotools提供的m4宏: AC_CHECK_LIB,这个如果探测成功,你需要令一些变量为那个探测的编译和链接参数。
(1.3)configure选项的实现:
想加一个--enable-debug 在configure脚本后,就能编译出debug版本么,那么在configure.ac里加入m4宏AC_ARG_ENABLE。这个你还要在宏里写明CXXFLAGS="$CXXFLAGS -g3 -D_DEBUG=1"。这也是上面提到的为什么automake需要和autoconf联用。Makefile需要靠autoconf来提供灵活性。
你用configure --help,就可以列出你定义的那些命令了。

(2)configure.ac对Makefile和源文件造成的影响:
对Makefile造成的影响,已经说明。
对源文件造成的影响,是源文件如果想利用探测结果,比如依靠预编译来区分哪些编译,哪些不编译,则必须包含configure生成的config.h,我想ratlib里的探测oss还是alsa,好象就是这样做的,但我没深究这个做法,但看来是正道:)

3. 自动化流程:
configure.ac和Makefile.am写好了之后,就需要执行一些命令,导致最后的configure脚本生成了,再靠用户执行它来生成各级的Makefile文件。
这些autotools命令呢,一般是自己写到一个脚本里面,取名叫autogen.sh
#!/bin/sh
set -x
aclocal
autoconf
autoheader      #产生config.h.in供configure使用
automake --foreign --add-missing -copy   #产生各级目录的Makefile.in供configure使用

(文档上是建议这个顺序)

(后来发现,还有些人直接用autoreconf -fvi,这个命令直接把上面的都执行一遍了,但我还是不喜欢,觉得自己一个一个敲,或者写成autogen.sh更clear一点)

4. libtool: 生成库文件:


附录:
1. 常用的autotool变量命名方式及写法举例:
需要知道的是,automake自定义了一套变量,而automake又可以用autoconf的变量,所以很多是相同的功能。
局部是指该makefile,局部的变量会替代全局的变量。
下面的用法大部分是正确的:)
(1)全局变量:
#为C编译器提供调试和优化选项
CFLAGS="-O2"
#为C预处理器提供头文件等选项
CPPFLAGS="-I/minisip/w32/include/ -I/home/Evan/Desktop/mixRunAPI/include/ -I/usr/include"
#为链接器提供的各种选项,但一般不在这里写-L和-l
LDFLAGS="-L/home/Evan/Desktop/mixRunAPI/lib/ -L/minisip/w32/lib/ -L/usr/lib"
#为链接器提供的-L和-l选项
LIBS="-L/home/Evan/lib -lm -lpthread"
(2)Makefile.am中的局部变量:(xx具体怎么写,要看规则,这里略)
xx_OBJECTS=hello.o       #如果目标是.o文件
xx_LIBRARIES=libhello.a #如果目标是静态库
xx_PROGRAMS=hello        #如果目标是可执行文件
xx_SOURCES=hello.c       #目标需要的所有文件
xx_LDADD=-lpthread -lm     #目标需要链接的库(空格隔开)
xx_LDADD=../lib/libhello.a #目标需要链接的库(同-l写法)
xx_INCLUDES=-I../include -I../../include #目标需要使用到的头件目录(空格隔开)
xx_CFLAGS=...    #对C编译器的优化,调试等选项
xx_CPPFLAGS=-I../include          #其中使用-I等于INCLUDES效果,这是CPPFLAGS正应该做的
xx_LDFLAGS=-L/home -la        #设置链接的库,而LDADD好象是可以多次使用的意思

****************************************************************************
autotools深入学习:
1. Windows从源文件到编译文件 VS Automake
在windows里面,其做法是让用户创建工程,并让用户在这个工程中创建或添加文件,这一步,会创建文件或者把用户指定的文件拷贝到这个工程的目录下,也就是拷贝到一个目录下而已(它也不能对分散的文件做处理,还是要拷过来)。如果用户要编译了,就对该工程每个子目录下找到的.c和.cpp文件(可能会按照用户指定的include文件夹找到头文件),生成.obj文件,然后将这些.obj文件都放一个该工程的一个子文件夹下(这个是用户可以调整的),这样以后链接的时候,很方便的了,如果用户新建立了一个工程,要test这个工程,系统就知道到这个文件夹里找.obj来链接。

而通过深入学习,发现automake系列不像想的那么弱,应该说它比windows那套编译系统强大,它支持两种Makefile文件的布局:一种是任意少的Makefile文件,比如一个;另一种是很正点的递归make;
(1)一个Makefile文件:
不管src在哪儿,只要你disable掉了dependency tracking feature,就可以实现keep src clean的编译方式。这个方式用VPATH不是可以实现么,其实VPATH实现的不彻底,VPATH说的是:在源码顶级目录按正规的目录结构创建各级Makefile.am,以及configure.ac(或按照(1)这样创造),最后生成configure文件。然后才是VPATH做的事,就是另外找个目录,然后执行../../xx/xx/configure 这样,就会在本目录生成想要的可执行文件或者库文件。所以我们可以看到,你仍旧要到源码目录去创造configure环境。

这里说的一个Makefile文件是这样的:
让源码目录保持清洁,然后另外自己找个目录,按照下面这样做:
a. 在源码顶级目录autoscan一下,得到的configure.scan文件拷贝到本目录,作为configure.ac模板:
注意加入AM_INIT_AUTOMAKE,修改AC_CONFIG_SRCDIR中写上一个到任意源码文件的路径,加上AC_PROG_RANLIB(因为这里是假设源码就是一个VC的工程,没有可执行文件,所以让它编译的所有.o合成一个库文件,我没找到一个写法,可以只让它生成很多.o文件的,也无所谓了),在AC_CONFIG_FILES加上Makefile。
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([../src/prt1/prt1.c])
AC_CONFIG_HEADER([config.h])

AC_PROG_RANLIB
# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile])
AC_OUTPUT

b. 在你找的目录里写Makefile.am:
用一个变量,如SRCDIR,来指代你的源码目录在哪儿,然后就按照下面这样写:
(你必须要指明生成的libhello.a是依赖哪些文件,其实,就是把一个VC工程下的所有文件的都写在这里,想想看,这相当于VC的工程文件浏览树,想加文件,减文件都在这里,唯一和VC不同的,就是通过修改Makefile.am来做)
(我们假设,在..的地方,有一个src文件夹,里面有prt1和prt2两个文件夹,而里面又分别有.c和.h文件。)
(如果要写的好看点,就每个源文件占一行,但是,每行末尾加上" /",表示下行还有)
SRCDIR = ..
noinst_LIBRARIES = libhello.a
libhello_a_SOURCES = $(SRCDIR)/src/prt1/prt1.h $(SRCDIR)/src/prt1/prt1.c $(SRCDIR)/src/prt2/prt2.h $(SRCDIR)/src/prt2/prt2.c

c. 写上autogen.sh:
要按照这个顺序写,因为已经知道automake要扫描autoconf来根据里面提到的所有Makefile来生成Makefile.in。
另一个必须注意的是:--ignore-deps,这个是这里(1)做法能够成功的关键。我不能太理解,因为这里的dependence指的是有关源文件的.h的依赖,我不太懂这个怎么就影响了这里(1)做法的实现。因为dep默认是打开,所以这里需要关闭。
#! /bin/sh
set -x
aclocal
autoconf
autoheader
automake --add-missing --ignore-deps

d. OK,开始autogen.sh,configure,make吧:
你会发现libhello.a就生成在你的这个目录下,不仅如此,所有的文件都在这下面,而源代码目录依旧保持清洁。

e. 问题:
要编译VC所谓的solution怎么办? 那就将Makefile.am中写多点文件贝呵呵。
或者创建多个文件夹,每个工程占用一个文件夹并创建Makefile.am,分别创建好.a库,之后再想办法链接就好,因为还没做,所以就先不写了。
实际上,我觉得如果可以每个工程一个Makefile,然后如果最后要联成什么,再一个Makefile的话,会显得好些。以后可以尝试着这样做。

NOTICE: 不排除用VPATH来替代这一做法的可能性。关于在makefile里写vpath可以参考write makefile.html。

(2)正点的递归:
这就是GNU的规范的源代码包中的目录结构了。我觉得,原则就是一个Makefile管理一个文件夹。
Automake 支持三种目录层次:'flat', 'shallow', 'deep'.
flat: 所有的文件都在一个目录中
deep: 所有的文件都在子目录中,上层的Makefile只包含 SUBDIRS = a b c
shallow:主要文件分布在顶层目录,而一些库等文件分布在子目录中,上层Makefile含有 SUBDIRS = a b c

与VC不同的是,你需要在automake下面以及每个子文件夹下写Makefile.am,因为SUBDIRS不允许含有“/”,就是说只允许写直接子目录。这里有两点:一是你需要自己写Makefile.am,二是每级目录下都得写Makefile.am。
如果在SUBDIRS中不含有.,则默认先编译子目录,如果含有.,则没有顺序了,可能会先编译本目录。
Makefile的方式一般是,一个Makefile管一个文件夹,所以,相关的文件都会生成在每个Makefile的同级目录,包括Makefile.in,.o,.a等文件。所以一个文件夹如果要想使用另一个文件夹的东西,比如依赖于一个.a库,那么就要写明,在下面的Makefile.am中会看到。
这里给个例子:
包目录下有src,src下有main.c和两个库文件夹prt1和prt2。
a. 写Makefile:
##在包目录下写Makefile.am:
SUBDIRS = src
##在src目录下写Makefile.am:
SUBDIRS = prt1 prt2
bin_PROGRAMS = hello
hello_SOURCES = main.c
hello_LDADD = prt1/libprt1.a prt2/libprt2.a #这一行表明main.c依赖东西要显示的指定,尽管还没编译出.a
##在prt1目录下写Makefile.am:
noinst_LIBRARIES = libprt1.a
libprt1_a_SOURCES = prt1.c
##在prt2目录下写Makefile.am:
noinst_LIBRARIES = libprt2.a
libprt2_a_SOURCES = prt2.c

b. 在包目录下autoscan:
将生成的文件改为configure.ac,你可以看到,AC_CONFIG_FILES里已经给你写好了要生成的所有的Makefile文件了!
再加AM_INIT_AUTOMAKE,以及AC_PROG_RANLIB就可以了。

c. 写上autogen.sh
(这里不用加--ignore-deps了)
#! /bin/sh
set -x
aclocal
autoconf
autoheader
automake --add-missing

d. OK了,autogen.sh,configure以及make吧:
没什么好说的了:)


**************************************************************************
man automake:
1. 运行automake必须要注意的:
根据设计者的思路:
(1)automake必须在和configure.in同级的目录下执行,即按照包下面是src, doc, test等目录结构的情况下,src所在的目录,也就是有些文章提到的“项目顶级目录”。因为设计者默认这样一种情况:automake是通过扫描configure.in的内容来产生Makefile.in。并且认为这个包只有一个configure.in。而产生Makefile.in也是默认为去寻找configure.in中提到的子目录(AC_OUTPUT或AC_CONFIG_FILES)。

2. 运行autoconfig和autoscan要注意的:
不知道。有一个 -B -I的选项,不知道有用否呵呵。



***************************************************************************
tips:
1. 指定automake和autoconf的版本:
export WANT_AUTOMAKE=1.7
export WANT_AUTOCONF=2.5.8
2. 手动指定预编译宏:
xx_CPPFLAGS = -Dmacro1 -Dmacro2
3. 源文件不用包含头文件路径:
源文件引用头文件,直接#include "aa.h" 就好了。
而在Makefile.am中这样指定:
xx_CPPFLAGS = -I../inc
4. 编译包含.c和.cpp的包:
(1)将CC置为g++
AC_PROG_CC
CC=g++
(2)解决(1)使用g++导致的对.c文件语法检查过于严格的问题:



*****************************************************************************
移植media库的经验:

1. 错误总结:
(1)无extern "C"
有些.c程序,为了给g++用,在头文件中有定义#ifdef __cplusplus来包含extern "C",所以你在用g++编译的时候,就要用-D选项指定__cplusplus,否则会报一些undefined reference to 的错误。
(这个语法只是给c++用的,gnu的c库的头文件没有包含这个,我想,可能是gnu的c库是g++编译的.o文件和gcc编译的.o文件的合集。)
(2)g++检查出n多转换类型错,这些错在windows编译器,或gcc上可能是不会报错的。
(3)用到了windows c 库特有的名字(有些函数和gnu c名字不同,但参数和功能相同)
(4)许多宏的定义,要一个一个去看,再给它打开
(5)许多包含的头文件名为小写,windows能认识,但g++不一定,因为系统中可能文件名不是小写的。

2. 实践得出的Makefile.am预定义宏的用法:
(1)xx_CPPFLAGS = -I -D #是我这次用到最多的参数。
(2)xx_LIBADD = *.o      #比如要生成noinst_LIBRARIES的话,这个可以将.o加到该库里。这个不是用做链接!


3. 关于含静态库的编译说明:
原则一定牢记:
(1)静态库的生成:
静态库的生成,不含有链接过程!!不管那些.o之间有没有依赖,静态库的生成做的就是将.o们合在一起,并且生成对.o中符号的index,供外界使用。
链接,是在生成可执行文件的时候,才做的,比如main.c要用到静态库中哪些.o,就会去找index,来判断该不该给你打印出:undefined reference to :)
用nm -s 可以列出库的index,再用grep可以查找你想要找的函数。
(换个角度说,因为生成库是不做链接检查的,所以你这个库,可能在之后被用于生成可执行程序时,会发现一些链接错误:undefined reference to ..,那说明你没有把一些函数加到某些.o里,比如哪些预编译条件没设好,当然也可能是需要其他的库)
(2).a的组成:
ar打入的东西,一定要是.o。就是说.a中含有的一定都是.o,不能有“更小的.a”。
这是因为,用ranlib建立.a的index的时候,只会对含有的.o建立index,会把所有的.o中的符号都放到index里面。所以,当你觉得可以将几个.a合为一个.a时候,一定要先ar x将.o们都拆出来,然后才能打为一个更到的.a。如果用automake,在写Makefile.am时,不要用xx_LIBADD,这个没用,只是把.a简单的包入而以(我没找到在automake里用哪个宏来打更大的包,其实打不打都无所谓)
(3)gcc/g++后的参数顺序:
如果是一堆.o文件,那么顺便哪个.o放哪儿,甚至,你用gcc/g++ *.o就好了。
但是,一旦涉及到静态库,那么就要注意了。
原则:按照依赖顺序,一个一个写在后面,libxx.a等于写-lxx。
比如你写了两个静态库lib1.a和lib2.a,还有main.c,而main.c要用到lib1.a的函数,lib1.a要用到lib2.a的函数,而lib2.a的函数要用到系统pthread,则就应该这样写:
g++ main.o lib1.a lib2.a -lpthread。
再次提醒,链接是在处理main,生成可执行程序时,而生成静态库只是用ar和ranlib将一群.o打成.a并建立index的过程,没有.a的聚集,也没有链接。生成.o也不需要任何链接。
注意,涉及到库的,不能用*.a来期望编译器给你调整顺序。

相关的建议:
(1)对于打包的建议:
静态库最好作成封闭性的,也就是,一个静态库除了依赖标准库外,不依赖于自己打的其他静态库,也就是把所有相依赖的.o都打成一个大包,这样就好了!否则,对自己和用户都不友好。至于一个.a中依赖性是怎么解决的,我想ld是这样做的:比如main.cpp要用到一个函数,那么它就去找.a的index,找到后发现该函数要调用另一个函数,于是ld继续找index中有没有另一个函数。
(2)对于编译可执行文件的建议:
尽量将源文件分为不依赖的几块,而这几块都被main使用。那几块分别作成.a。
这是最好的情况。如果有依赖,注意调整gcc/g++后.a的顺序。




你应该学习一下,别人多层次的makefile.am是怎么写的,这样的好处是模块清晰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值