分类: 嵌入式
2010-01-04 18:14:52
make和Makefile提供了一种非常简单有效的工程管理方式。使用这种方式管理工程的原理很简单:Makefile是一个决定怎样编译工程的文本文件,有一定的书写规则。在工程更新的时候,使用GNU的make工具根据当前的Makefile对工程进行编译。
在Linux的程序开发环境下,一般不具有集成开发环境(IDE)。因此,当需要大量编译工程文件的时候,就需要使用自己的方法来管理。如果仅仅手动使用gcc的编译命令,将变得烦琐而单调,而且不利于工程管理。而如果使用Makefile进行工程管理,就可以较好地处理这个问题。
make 程序最初设计的目的是为了维护C程序文件,防止不必要的重新编译。例如,在使用命令行进行编译的时候,修改了一个工程中的头文件,如何确保包含这个头文件的所有文件都得到编译呢?这些工作可以让make程序来自动完成。make工具对于维护一些具有相互依赖关系的文件特别有用,它对文件和命令的联系(在文件改变时调用来更新其他文件的程序)提供一套编码方法。make工具的基本概念类似于Proglog语言,在使用的过程中只告诉make需要做什么,即提供一些规则,其他的工作由make自动完成。
make工具的工作是自动确定工程的哪部分需要重新编译,然后执行命令去编译它们。虽然这种方式多用于C程序,然而只要提供命令行的编译器,就可以将其用于任何语言。实际上,make工具不仅应用于编程,也可以用于描述一些文件改变时,需要自动更新另一些文件的任务。在程序开发的过程中,Makefile带来的好处就是自动化编译。当编译规则制定完成后,只需要一个make命令,整个工程就会根据Makefile判断是否需要更新来完成自动编译,极大地提高了软件开发的效率,降低了开发的复杂度。
make机制的运行环境需要一个命令行程序make和一个文本文件Makefile。
make是一个命令工具,具体来说是一个解释Makefile中的指令的命令工具。Makefile的工作原理是调用系统中的make命令解释当前的Makefile,完成其中指定的功能。在很多的IDE中都有这个命令,如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile已经成为一种在工程方面的编译方法。
Makefile里主要包含了5个方面的内容:显式规则、隐式规则、变量定义、文件指示和注释。
1.显式规则。显式规则说明了如何生成一个或多个目标。这需要由Makefile的书写者显式指出要生成的文件、文件的依赖文件及生成的命令。
2.隐式规则。由于make有自动推导的功能,会选择一套默认的方法进行make,所以隐式的规则可以让开发者比较、简略地书写Makefile,这是由make所支持的。
3.变量定义。在Makefile中需要定义一系列的变量,一般都是字符串,它类似C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4.文件指示。包括三个部分,第一部分是在一个Makefile中引用另一个Makefile,就像C语言中的include一样包含进来;第二部分是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译宏#ifdef一样;第三部分就是定义一个多行的命令。
5.注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释符使用井号“#”字符,这个就像C/C++中的双斜杠“//”一样。如果需要在Makefile中使用井号“#”字符,可以用反斜杠进行转义,如:“\\#”。
make是一个Linux下的二进制程序,用来处理Makefile这种文本文件。在Linux的Shell命令行键入make的时候,将自动寻找名称为“Makefile”的文件作为编译文件,如果没有名称为“Makefile”的文件,将继续查找名称为“makefile”的文件。找到编译文件后,make工具将根据Makefile中的第一个目标自动寻找依赖关系,找出这个目标所需要的其他目标。如果所需要的目标也需要依赖其他的目标,make工具将一层层寻找直到找到最后一个目标为止。
make工具的使用格式为:
make [options] [target] ...
options为make工具的选项,target为Makefile中指定的目标。表5-1给出了make工具的参数选项。
表5-1 make工具的参数选项
选 项 |
含 义 |
-f filename |
显式地指定文件作为Makefile |
-C dirname |
制定make在开始运行后的工作目录为dirname |
-e |
不允许在Makefile中替换环境变量的赋值 |
-k |
执行命令出错时,放弃当前目标,继续维护其他目标 |
-n |
按实际运行时的执行顺序模拟执行命令(包括用@开头的命令),没有实际执行效果,仅仅用于显示执行过程 |
-p |
显示Makefile中所有的变量和内部规则 |
-r |
忽略内部规则 |
-s |
执行但不显示命令,常用来检查Makefile的正确性 |
-S |
如果执行命令出错就退出 |
-t |
修改每个目标文件的创建日期 |
-I |
忽略运行make 中执行命令的错误 |
-V |
显示make的版本号 |
在Makefile中,经常使用的变量如表5-2所示。
表5-2 Makefile中常用变量
变 量 |
描 述 |
$@ |
目标文件名 |
$< |
规则中的第一个文件名 |
$^ |
规则中所有相关文件的名称 |
$? |
规则中日期比目标新的文件列表,用空格分开 |
$@(D) |
目标文件的目录部分 |
$@(F) |
目标文件的文件名部分 |
在Makefile中,目标名称的指定常常有以下惯例:
· all:表示编译所有的内容,是执行make时默认的目标。
· clean:表示清除目标。
· distclean:表示清除所有的内容。
· install:表示进行安装的内容。
本节的Makefile是一个简单的示例,它只用于显示信息,并不生成具体的目标。文件如下所示:
all:
@echo "+++++++ make all +++++++"
rule0:
@echo "Input = $(INPUT)"
@echo 'This Target is $@'
.PHONY : clean
clean:
@echo "------- clean ------"
在该文件中,每一条语句的@echo 'This Target is $@'的前面需要使用Tab键作为开头,这是Makefile书写的要求。
在命令行键入“make”:
$ make
+++++++ make all +++++++
执行的结果是Makefile文件中的all规则。注意,在文件命令行中使用@,表示不在命令行显示该程序的运行输出状态,对于没有使用@标注的命令,在执行make的时候,命令行的内容将显示在屏幕上。
使用clean:
$ make clean
------- clean ------
此时,make工具根据Makefile中的clean目标执行相关的内容。
以上执行make和make clean是使用make工具的时候最常见的方式,make默认执行all目标,make clean表示清除目标。
对于Makefile中的其他目标,可以通过在命令行指定让其得到执行,如下所示:
$ make rule0
Input =
This Target is rule0
此时,明确指定执行rule0目标。由于使用的变量$(INPUT)没有初始值,因此打印出的内容为空。
在Makefile中,可以使用“变量=值”的方式,在命令行指定执行过程中变量的值。如下所示:
$ make rule0 INPUT=abcde
Input = abcde
This Target is rule0
此时由于命令行指定了INPUT=abcde,因此在执行的过程中$(INPUT)变量的值为abcde,在执行echo时输出了这个值。由此,对于同一个Makefile文件,其执行的结果,可以根据命令行的参数进行选择,由此实现其可配置的特性。
依赖关系是Makefile在执行过程中的核心内容。在应用中Makefile不仅可以用于编译,也可用于处理其他的逻辑,本节以一个Makefile为例,说明在执行make工作中处理依赖关系的过程。
Makefile文件如下所示:
all:rule0 file.o
rule0:rule1
@echo "+++++++ rule0 +++++++"
@echo 'The deps:$^'
@echo 'The target:$@'
rule1:rule2
@echo "+++++++ rule1 +++++++"
rule2:rule3
@echo "+++++++ rule2 +++++++"
rule3:
@echo "+++++++ rule3 +++++++"
file.o:
@echo "1234567890" > file.o
@echo "File path: $(@D) File name : $(@F)"
.PHONY : clean rule0 rule1 rule2 rule3
clean:
@echo "------- clean ------"
rm -f file.o
在这个Makefile的路径下,执行make命令:
$ make
执行后显示的结果为:
+++++++ rule3 +++++++
+++++++ rule2 +++++++
+++++++ rule1 +++++++
+++++++ rule0 +++++++
The deps:rule1
The target:rule0
File path: . File name : file.o
由于make没有指定选项和目标,将默认使用Makefile文件,并执行其中的all目标。在执行的过程中,首先发现all目标依赖于rule0和file.o两个目标,因此需要完成这两个目标的处理。对于rule0目标,依次寻找它的依赖关系,直到找到rule3目标,然后再从rule3目标执行,依次执行rule3,rule2,rule1,rule0。对于file.o目标,将生成file.o文件,它由file.o目标生成,内容为"1234567890",变量@D表示目标的所在目录的路径,@F表示目标的文件名。
在规则rule0:rule1中,使用了变量$^和$@,前者表示依赖的所有文件,后者表示目标的名称。事实上,Makefile的执行顺序不是按照每条规则书写的先后顺序,而是由规则之间的依赖关系确定的。
在Makefile中,将目标clean rule0 rule1 rule2 rule3定义为伪目标(.PHONY),这是由于它们不是需要生成的内容的名称;file.o是实际生成的结果,因此它是真实的目标,而不是伪目标。
在执行过一次make之后,再次执行make命令,得到的结果如下所示:
+++++++ rule3 +++++++
+++++++ rule2 +++++++
+++++++ rule1 +++++++
+++++++ rule0 +++++++
The deps:rule1
The target:rule0
从执行结果中可见,这次只执行了rule0及其依赖的目标,没有执行目标file.o。这是由于目标file.o依赖的内容没有变化,所以这条目标不需要被执行,这说明了Makefile条件编译的特性。
执行make clean的结果如下所示:
$ make clean
------- clean ------
rm -f file.o
这次执行删除了file.o文件,状态已经退回到make执行之前。因此再次执行make的时候,将和首次执行是一致的。
在make命令的使用中,可以使用-n选项显示执行的序列:
$ make –n
本次执行的结果为:
echo "+++++++ rule3 +++++++"
echo "+++++++ rule2 +++++++"
echo "+++++++ rule1 +++++++"
echo "+++++++ rule0 +++++++"
echo 'The deps:rule1'
echo 'The target:rule0'
echo "1234567890" > file.o
echo "File path: . File name : file.o"
由此可见,在本次的执行中,只显示了需要执行的命令,而不是真正地执行这些命令。在这个过程中,寻找依赖关系的过程和直接的make过程是一致的,但是只显示要执行命令而不执行命令。
在使用make的过程中,也可以指定一条单独的目标来执行,例如:
$ make rule2
+++++++ rule3 +++++++
+++++++ rule2 +++++++
这时,将指定目标rule2来执行,执行的过程发现它依赖于目标rule3,因此先执行rule3的内容,再执行目标rule2的内容。对于其他的目标则不需要执行。
本示例演示一个程序的生成过程,使用的程序文件为第4章中的文件。Makefile文件和程序文件在一个文件夹中。Makefile文件如下所示:
CC := gcc
HEAD := getarg.h
SRC := getarg.c writeinfo.o main.c
OBJS := getarg.o writeinfo.o main.o
TT := test
Files := $(wildcard ./*)
INC = .
CFLAGS = -pipe -g -Wall -I$(INC)
LDFLAGS = -Wall -g
all:$(TT)
$(TT):$(OBJS)
@echo "+++++++ Build Standalone Programe : $@ +++++++"
$(CC) $(LDFLAGS) $(OBJS) -o $@
libtest_d.so:getarg.o writeinfo.o
@echo "+++++++ Build Dynamic lib : $@ +++++++"
$(CC) -shared $(LDFLAGS) getarg.o writeinfo.o -o $@
test_dlib:libtest_d.so main.o
@echo "+++++++ Build Exe by Dynamic lib : $@ +++++++"
$(CC) $(LDFLAGS) main.o -L. -ltest_d -o $@
filelist:
@echo "<<<<<<< Files in this folder >>>>>>"
@file $(Files)
.PHONY : clean filelist
%.o:%c
$(CC) $(CFLAGS) -c $< -o $@
clean:
@echo "------- clean ------"
rm -f *.o
rm -f $(TT)
rm -f libtest_d.so
rm -f test_dlib
在本例中,定义了CC等变量,在变量引用和使用这些变量的时候,需要用$(CC)的形式。
在Makefile和源文件所在目录中,在命令行执行make命令:
$ make
执行的结果如下所示:
gcc -pipe -g -Wall -I. -c -o getarg.o getarg.c
gcc -pipe -g -Wall -I. -c -o writeinfo.o writeinfo.c
gcc -pipe -g -Wall -I. -c -o main.o main.c
+++++++ Build Standalone Programe : test +++++++
gcc -Wall -g getarg.o writeinfo.o main.o -o test
在执行的过程中,默认执行all目标,由于all目标依赖于变量$(TT),$(TT)实际上是test。$(TT)依赖于$(OBJS),$(OBJS)就是getarg.o writeinfo.o main.o。因此,需要产生这三个目标文件。
上述make工作的处理过程是这样的:首先寻找三个目标文件(getarg.o,writeinfo.o和main.o)的生成规则。在所有的规则中,并没有这三个目标文件的生成规则,因此使用默认的目标%.o:%c中的规则生成这三个目标文件。这个时候会使用gcc编译生成这三个目标文件。生成完三个目标文件之后,将执行test目标,进行目标文件的连接。
事实上,上述执行过程中只是直接执行了all目标,在Makefile中还有libtest_d.so、test_dlib和filelist几个目标没有执行,而这些目标可以单独执行。
执行单独的目标filelist:
$ make filelist
显示的结果如下:
<<<<<<< Files in this folder >>>>>>
./getarg.c: ASCII C program text
./getarg.h: ASCII text
./getarg.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
./main.c: ASCII C program text
./main.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
./Makefile: ASCII make commands text
./test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.4, dynamically linked (uses shared libs), for GNU/Linux 2.6.4, not stripped
./writeinfo.c: ASCII C program text
./writeinfo.h: ASCII text
./writeinfo.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
这条目标执行的命令是使用file命令查看本文件夹下所有的文件。其中,Files := $(wildcard ./*)表示使用通配符寻找目录下的所有文件。
执行生成可执行程序test_dlib的命令:
$ make test_dlib
执行的结果如下所示:
+++++++ Build Dynamic lib : libtest_d.so +++++++
gcc -shared -Wall -g getarg.o writeinfo.o -o libtest_d.so
+++++++ Build Exe by Dynamic lib : test_dlib +++++++
gcc -Wall -g main.o -L. -ltest_d -o test_dlib
test_dlib目标是一个可执行程序,它本身需要连接一个动态库libtest_d.so,因此它依赖于目标libtest_d.so和main.o目标,由于main.o已经生成,这样还需要生成libtest_d.so目标。在libtest_d.so目标中,依赖的文件getarg.o和writeinfo.o都已经生成了,因此直接生成这个动态库即可。libtest_d.so生成后,再生成 test_dlib可执行程序。
在以上的示例中的test和test_dlib都是可执行的程序,它们的区别在于前者包含了三个目标文件,可以直接执行,后者只包括了main.o一个目标文件,它的执行必须依赖动态库。
继续使用clean清除目标:
$ make clean
执行的结果如下所示:
------- clean ------
rm -f *.o
rm -f test
rm -f libtest_d.so
rm -f test_dlib
在清除目标之后,生成test_dlib可执行程序:
$ make test_dlib
gcc -pipe -g -Wall -I. -c -o getarg.o getarg.c
gcc -pipe -g -Wall -I. -c -o writeinfo.o writeinfo.c
+++++++ Build Dynamic lib : libtest_d.so +++++++
gcc -shared -Wall -g getarg.o writeinfo.o -o libtest_d.so
gcc -pipe -g -Wall -I. -c -o main.o main.c
+++++++ Build Exe by Dynamic lib : test_dlib +++++++
gcc -Wall -g main.o -L. -ltest_d -o test_dlib
在这次执行的过程中,由于getarg.o,writeinfo.o和main.o三个目标文件还没有生成,因此在生成库libtest_d.so之前,需要先编译生成getarg.o和writeinfo.o两个目标,它们使用的是默认的规则。在libtest_d.so生成后,还需要生成main.o的过程,它也需要使用默认的规则。
知识点:通常情况下,为了加速开发,程序员自己编写的程序在无特殊要求下都可以使用隐含规则,这样还可以防止因为Makefile编写错误而导致程序运行错误。
仍以第4章的程序文件为例,本节介绍另外的一种形式的Makefile,在这个Makefile中指定了各个目标的依赖关系。该文件如下所示:
CC := gcc
HEAD := getarg.h writeinfo.h
SRC := getarg.c writeinfo.o main.c
OBJS := getarg.o writeinfo.o main.o
TT := test
INC = .
CFLAGS = -pipe -g -Wall -I$(INC)
LDFLAGS = -Wall -g
all:$(TT)
$(TT):$(OBJS)
@echo "+++++++ Build Standalone Programe : $@ +++++++"
$(CC) $(LDFLAGS) $(OBJS) -o $@
main.o:main.c getarg.h writeinfo.h
$(CC) $(CFLAGS) -c $< -o $@
getarg.o:getarg.c getarg.h
$(CC) $(CFLAGS) -c $< -o $@
writeinfo.o:writeinfo.c writeinfo.h
$(CC) $(CFLAGS) -c $< -o $@
.PHONY : clean
clean:
@echo "------- clean ------"
rm -f *.o
rm -f $(TT)
在这个Makefile文件中没有使用默认的规则,而是对每一个目标文件实现单独的规则和依赖的文件。
在Makefile文件所在目录下执行make命令:
$ make
执行的结果如下所示:
gcc -pipe -g -Wall -I. -c getarg.c -o getarg.o
gcc -pipe -g -Wall -I. -c writeinfo.c -o writeinfo.o
gcc -pipe -g -Wall -I. -c main.c -o main.o
+++++++ Build Standalone Programe : test +++++++
gcc -Wall -g getarg.o writeinfo.o main.o -o test
上面执行的结果依然是先编译生成目标文件,然后连接生成可执行程序。实际上,这个执行过程依次执行了getarg.o,writeinfo.o,main.o和 test的规则。
在目标生成之后,如果再次使用make命令,将显示以下的内容:
$ make
make: Nothing to be done for 'all'.
此时由于目标依赖的文件均没有更改,因此没有什么需要做的。更新main.c文件,继续执行make命令:
$ touch main.c
$ make
结果如下所示:
gcc -pipe -g -Wall -I. -c main.c -o main.o
+++++++ Build Standalone Programe : test +++++++
gcc -Wall -g getarg.o writeinfo.o main.o -o test
在执行的过程中,可以发现先后执行了main.o和test目标中的规则。这是由于main.o目标依赖于main.c文件,因此main.c更新后,这个目标就需要重新生成。getarg.o和writeinfo.o目标的规则是不需要执行的,因为它们依赖的文件没有更新。
下面对getarg.h文件进行更新,然后重新生成,命令如下:
$ touch getarg.h
$ make
执行结果如下所示:
gcc -pipe -g -Wall -I. -c getarg.c -o getarg.o
gcc -pipe -g -Wall -I. -c main.c -o main.o
+++++++ Build Standalone Programe : test +++++++
gcc -Wall -g getarg.o writeinfo.o main.o -o test
在这次执行的过程中,由于getarg.o和main.o目标都依赖getarg.h文件,因此,两个目标的规则都需要重新执行。
执行清除命令clean:
$ make clean
执行的结果如下所示:
------- clean ------
rm -f *.o
rm -f test
这里make工具执行的是clean伪目标,删除了目标文件和可执行程序。
知识点:make和Makefile工程管理工具,可以使用隐含规则进行编译,也可以由Makefile编写者制定自己特定的编译规则。
在实际的项目中,由于make规则的复杂性和不确定性,自己编写Makefile是一件费时费力的事情。Makefile本身具有一定的相似性,因此利用GNU autoconf及automake这两套工具可以协助我们自动产生Makefile文件,并且让开发出来的软件可以像大多数源代码包那样,只需运行命令“./configure”、“make”、“make install”就可以把程序安装到系统中,对于各种源代码包的分发和兼容性具有很好的效果。
autoconf是一个用于产生可以自动配置源代码包,生成Shell脚本的工具,它可以适应各种类UNIX系统的需要。autoconf产生的配置脚本在运行时独立于autoconf,也就是说使用这些脚本的用户不需要安装autoconf。
autoconf生成的配置脚本通常名称是configure,得到这个文件,通常需要以下的依赖文件:
· configure.in文件:生成configure的必需文件,需要手动编写。
· aclocal.m4和acsite.m4文件:在编写了除autoconf提供的测试外的其他测试补充的时候,才会用到这两个文件,也需要手动编写。
· acconfig.h文件:如果使用了含有#define指令的头文件,则需要自行编写该文件,一般都需要使用,这个时候会生成另外一个config.h.in文件,这个文件需要和软件包一同发布。
总之,在autoconf运行完毕后,得到两个需要和软件包同时发布的文件: configure和config.h.in,当然config.h.in可以不存在。
automake是一个从文件Makefile.am自动生成Makefile.in的工具。每个Makefile.am基本上是一系列make
的宏定义(make规则也会偶尔出现)。生成的Makefile.in也服从GNU Makefile标准。
典型的automake输入文件是一系列简单的宏定义。处理所有相关的文件并创建Makefile.in文件。在一个项目的每个目录中通常仅包含一个Makefile.am。
目前automake支持三种目录层次:平坦模式(flat)、混合模式(shallow)和深层模式(deep)。
(1)平坦模式指的是所有文件都位于同一个目录中。就是所有源文件、头文件及其他库文件都位于当前目录中,且没有子目录。
(2)混合模式指的是主要的源代码都存储在顶层目录,其他各个部分则存储在子目录中。也就是主要源文件在当前目录中,而其他一些实现各部分功能的源文件位于各自不同的目录。
(3)深层模式指的是所有源代码都被存储在子目录中;顶层目录主要包含配置信息。也就是所有源文件及程序员自己写的头文件都位于当前目录的一个子目录中,而当前目录里没有任何源文件。
在这三种支持的目录层次中,平坦模式类型是最简单的,深层模式类型是最复杂的。但是这些模式使用autoconf和automake所遵循的基本原则和流程是一样的。
(1)autoheader:能够产生供configure脚本使用的C #define语句模板文件。
(2)autom4te:对文件执行 GNU M4。
(3)autoreconf:如果有多个autoconf产生的配置文件,autoreconf可以保存一些相似的工作,它通过重复运行autoconf(以及在合适的地方运行autoheader)以重新产生autoconf配置脚本和配置头模板,这些文件保存在以当前目录为根的目录树中。
(4)autoscan:该程序可以用来为软件包创建configure.in文件。autoscan在以命令行参数中指定的目录为根(如果未给定参数,则以当前目录为根)的目录树中检查源文件。为软件包创建一个configure.scan文件,该文件就是configure.in的前身。
(5)autoupdate:该程序的作用是转换configure.in,从而使用新的宏名。
在进行自动化生成Makefile之前,务必要设定好工作的根目录,在当前环境下,至少要保证autoscan、autoconf、aclocal、automake这些命令能够正常运行。在这一节中,我们就以一个最简单的示例来说明automake和autoconf的基本使用方法,这个例子是一个平坦模式的模型。自动生成Makefile的流程如图5-1所示。
图5-1 自动生成Makefile的流程
在这里使用标准的sntp客户端源代码进行示例,该源代码只有一个源文件和一个头文件,分别是ntp.h和sntp.c。
(1)首先把所有的源代码文件复制到当前的根目录下,然后运行autoscan扫描所有的代码,并得到autoscan.log和configure.scan两个文件。
其中autoscan.log是一个空文件,而configure.scan文件则需要用户手动进行编辑,在该例中,修改之前configure.scan的内容应该如下:
#-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([sntp.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([netdb.h netinet/in.h stdlib.h string.h sys/socket.h sys/time.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_TIME
AC_STRUCT_TM
# Checks for library functions.
AC_FUNC_ERROR_AT_LINE
AC_FUNC_MALLOC
AC_FUNC_SELECT_ARGTYPES
AC_CHECK_FUNCS([bzero gethostbyname gettimeofday select socket strchr])
AC_OUTPUT
这是一个标准的config模板,后面针对configure文件的修改都是基于这个文件进行的,configure.scan的基本格式如下:
AC_INIT
测试程序 AC_PROG_CC
测试函数库 (在源文件中没有用到函数库)
测试头文件 AC_HEADER_STDC,AC_CHECK_HEADERS
测试类型定义 AC_HEADER_TIME
测试结构 AC_STRUCT_TM
测试编译器特性
测试库函数 AC_FUNC_ERROR_AT_LINE,AC_FUNC_MALLOC
AC_FUNC_SELECT_ARGTYPES
测试系统调用 AC_CHECK_FUNCS
AC_OUTPUT
这个文件中有几个非常重要的语句,一般来说所有的configure.scan文件都是以AC_INIT开头和以AC_OUTPUT结束的;而且中间的顺序一般不要进行随意的改变,因为通常在本列表中靠后的项目依赖于表中靠前的项目。例如,库函数可能受到typedefs和库的影响。
(2)configure.scan文件准备好之后,就开始准备把它改造成自己定制的configure.in文件,先把它的名字修改为configure.in,然后打开该文件进行编辑。在configure.in文件中,必须修改的内容有:
· 将AC_CONFIG_HEADER([config.h])修改为AM_CONFIG_HEADER([config.h]),也就是说把AC改成AM。
· 在AM_CONFIG_HEADER下面添加一行AM_INIT_AUTOMAKE(sntp,1.0),这一行命令说明了使用automake最终要得到的结果和版本号。
· 如果工程中使用了外部的库,比如pthread线程库,在# Checks for libraries这一行的下面还会生成像下面这样的行:
AC_CHECK_LIB([pthread],[pthread_rwlock_init])
· 最后,还要在AC_OUTPUT后面加上要创建的文件名称:AC_OUTPUT([Makefile])。
· 如果是混合模式或者是深层模式的结构,还需要添加子目录的目标Makefile文件路径到AC_OUTPUT后面的输入中。
此时,configure.in文件的最终内容应该如下:
#-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT(sntp, 1.0, author@gmail.com)
AC_CONFIG_SRCDIR([sntp.c])
AM_CONFIG_HEADER([config.h])
AM_INIT_AUTOMAKE(sntp, 1.0)
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([netdb.h netinet/in.h stdlib.h string.h sys/socket.h sys/time.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_TIME
AC_STRUCT_TM
# Checks for library functions.
AC_FUNC_ERROR_AT_LINE
AC_FUNC_MALLOC
AC_FUNC_SELECT_ARGTYPES
AC_CHECK_FUNCS([bzero gethostbyname gettimeofday select socket strchr])
AC_OUTPUT
完成了configure.in文件的编写后,还有一个文件需要用户手动编写,就是Makefile.am文件,针对本工程的一个标准的简易Makefile.am模板如下:
AUTOMAKE_OPTIONS = foreign
# Note:target part
bin_PROGRAMS = sntp
# noinst_PROGRAMS = sntp
# Note:source part
sntp_SOURCES = sntp.c
# sntp_LDADD =
# sntp_LDFLAGS =
# sntp_DEPENDENCIES =
# Note:lib part
# lib_LIBRARIES =
# sntp_a_SOURCES =
# sntp_a_LDADD =
# sntp_a_LIBADD =
# sntp_a_LDFLAGS =
# Note:header part
include_HEADERS = ntp.h
# Note:data part
# data_DATA =
可以看到Makefile.am文件主要由五个部分组成,分别是目标描述部分、源代码描述部分、库描述部分、头描述部分和数据描述部分。在本例中,由于只有源代码文件和头文件,没有数据和依赖的库,所以对不用的段用井号“#”进行注释。
在目标描述部分,一般有两种选择,如果描述为bin_PROGRAMS,则表明该程序是需要进行安装的,而如果描述为noinst_PROGRAMS,则表明不需要安装。
一般来说,如果不显式地进行声明,默认的几个全局路径如下:
· 安装路径前缀:$(prefix) = /usr/local
· 目标文件安装路径:bindir = $(prefix)/bin
· 库文件安装路径:libdir = $(prefix)/lib
· 数据文件安装路径:datadir = $(prefix)/share
· 系统配置安装路径:sysconfdir = $(prefix)/etc
写完了Makefile.am文件之后,生成Makefile的前期准备工作就做完了。接下来就是使用auto系列工具生成的过程,按照如下顺序输入命令:
$./aclocal # 得到aclocal.m4文件
$./autoconf # 得到configure文件
$./automake –a # 得到Makefile.in文件
到此为止,就完成了所有自动化的配置任务,把生成的一系列相关文件压缩打包,就可以进行版本的发布了。
用户拿到这个安装包解压后,就可以按照一般软件包的方式进行Makefile的最后生成、编译和安装,也就是输入以下命令:
$ ./configure (得到makefile文件)
$ make
$ make install
知识点:使用autoconf和automake来进行自动化配置和生成Makefile的流程可以概括如下:
(1)运行autoscan命令。
(2)将configure.scan文件重命名为configure.in,并修改configure.in文件。
(3)运行aclocal命令得到aclocal.m4文件。
(4)运行autoconf命令得到configure文件。
(5)在工程目录下新建Makefile.am文件,如果存在子目录,子目录中也要创建此文件。
(6)将/usr/share/automake-1.X/目录下的depcomp和compile文件复制到需要处理目录下。
(7)运行automake -a命令得到Makefile.in文件。
(8)运行./configure脚本(这一步已经属于使用自动化管理的范畴了)。