详细介绍Makefile的用法

文章出自:http://huiya1983.blog.163.com/blog/static/1365821692010229112253788/?fromdm&fromSearch&isFromSearchEngine=yes

5.1  makeMakefile

makeMakefile提供了一种非常简单有效的工程管理方式。使用这种方式管理工程的原理很简单:Makefile是一个决定怎样编译工程的文本文件,有一定的书写规则。在工程更新的时候,使用GNUmake工具根据当前的Makefile对工程进行编译。

5.1.1  make机制概述

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中都有这个命令,如:DelphimakeVisual C++nmakeLinuxGNUmake。可见,Makefile已经成为一种在工程方面的编译方法。

Makefile里主要包含了5个方面的内容:显式规则、隐式规则、变量定义、文件指示和注释。

1.显式规则。显式规则说明了如何生成一个或多个目标。这需要由Makefile的书写者显式指出要生成的文件、文件的依赖文件及生成的命令。

2.隐式规则。由于make有自动推导的功能,会选择一套默认的方法进行make,所以隐式的规则可以让开发者比较、简略地书写Makefile,这是由make所支持的。

3.变量定义。在Makefile中需要定义一系列的变量,一般都是字符串,它类似C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4.文件指示。包括三个部分,第一部分是在一个Makefile中引用另一个 Makefile,就像C语言中的include一样包含进来;第二部分是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译宏#ifdef一样;第三部分就是定义一个多行的命令。

5.注释。Makefile中只有行注释,和UNIXShell脚本一样,其注释符使用井号“#”字符,这个就像C/C++中的双斜杠“//”一样。如果需要在Makefile中使用井号“#”字符,可以用反斜杠进行转义,如:“\\#”

5.1.2  makeMakefile的使用

make是一个Linux下的二进制程序,用来处理Makefile这种文本文件。在LinuxShell命令行键入make的时候,将自动寻找名称为“Makefile”的文件作为编译文件,如果没有名称为“Makefile”的文件,将继续查找名称为“makefile”的文件。找到编译文件后,make工具将根据Makefile中的第一个目标自动寻找依赖关系,找出这个目标所需要的其他目标。如果所需要的目标也需要依赖其他的目标,make工具将一层层寻找直到找到最后一个目标为止。

make工具的使用格式为:

make [options] [target] ...

optionsmake工具的选项,targetMakefile中指定的目标。表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:表示进行安装的内容。

 

 

5.2  Makefile使用示例

5.2.1  简单的Makefile

本节的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目标执行相关的内容。

以上执行makemake 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文件,其执行的结果,可以根据命令行的参数进行选择,由此实现其可配置的特性。

5.2.2  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目标依赖于rule0file.o两个目标,因此需要完成这两个目标的处理。对于 rule0目标,依次寻找它的依赖关系,直到找到rule3目标,然后再从rule3目标执行,依次执行 rule3rule2rule1rule0。对于file.o目标,将生成file.o文件,它由file.o目标生成,内容为"1234567890",变量@D表示目标的所在目录的路径,@F表示目标的文件名。

在规则rule0:rule1中,使用了变量$^$@,前者表示依赖的所有文件,后者表示目标的名称。事实上,Makefile的执行顺序不是按照每条规则书写的先后顺序,而是由规则之间的依赖关系确定的。

Makefile中,将目标clean rule0 rule1 rule2rule3定义为伪目标(.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: . Filename : file.o"

由此可见,在本次的执行中,只显示了需要执行的命令,而不是真正地执行这些命令。在这个过程中,寻找依赖关系的过程和直接的make过程是一致的,但是只显示要执行命令而不执行命令。

在使用make的过程中,也可以指定一条单独的目标来执行,例如:

$ make rule2

+++++++ rule3 +++++++

+++++++ rule2 +++++++

这时,将指定目标rule2来执行,执行的过程发现它依赖于目标rule3,因此先执行rule3的内容,再执行目标rule2的内容。对于其他的目标则不需要执行。

5.2.3  Makefile中使用隐含规则来编译程序

本示例演示一个程序的生成过程,使用的程序文件为第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 thisfolder >>>>>>"

    @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.owriteinfo.o main.o)的生成规则。在所有的规则中,并没有这三个目标文件的生成规则,因此使用默认的目标%.o:%c中的规则生成这三个目标文件。这个时候会使用gcc编译生成这三个目标文件。生成完三个目标文件之后,将执行test目标,进行目标文件的连接。

事实上,上述执行过程中只是直接执行了all目标,在Makefile中还有libtest_d.sotest_dlibfilelist几个目标没有执行,而这些目标可以单独执行。

执行单独的目标filelist

$ make filelist

显示的结果如下:

<<<<<<< Files in this folder >>>>>>

./getarg.c:       ASCII C program text

./getarg.h:       ASCII text

./getarg.o:       ELF 32-bit LSBrelocatable, Intel 80386, version 1 (SYSV), not stripped

./main.c:       ASCII C program text

./main.o:        ELF 32-bit LSBrelocatable, Intel 80386, version 1 (SYSV), not stripped

./Makefile:      ASCII make commands text

./test:          ELF 32-bitLSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.4, dynamicallylinked (uses shared libs), for GNU/Linux 2.6.4, not stripped

./writeinfo.c:        ASCII C programtext

./writeinfo.h:        ASCII text

./writeinfo.o:        ELF 32-bit LSBrelocatable, 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.somain.o目标,由于main.o已经生成,这样还需要生成 libtest_d.so目标。在libtest_d.so目标中,依赖的文件getarg.owriteinfo.o都已经生成了,因此直接生成这个动态库即可。libtest_d.so生成后,再生成 test_dlib可执行程序。

在以上的示例中的testtest_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.omain.o三个目标文件还没有生成,因此在生成库libtest_d.so之前,需要先编译生成getarg.owriteinfo.o两个目标,它们使用的是默认的规则。在libtest_d.so生成后,还需要生成main.o的过程,它也需要使用默认的规则。

知识点:通常情况下,为了加速开发,程序员自己编写的程序在无特殊要求下都可以使用隐含规则,这样还可以防止因为Makefile编写错误而导致程序运行错误。

5.2.4  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. -cwriteinfo.c  -o writeinfo.o

gcc -pipe -g -Wall -I. -c main.c  -omain.o

+++++++ Build Standalone Programe : test +++++++

gcc -Wall -g getarg.o writeinfo.o main.o  -o test

上面执行的结果依然是先编译生成目标文件,然后连接生成可执行程序。实际上,这个执行过程依次执行了getarg.owriteinfo.omain.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  -omain.o

+++++++ Build Standalone Programe : test +++++++

gcc -Wall -g getarg.o writeinfo.o main.o  -o test

在执行的过程中,可以发现先后执行了main.otest目标中的规则。这是由于main.o目标依赖于main.c文件,因此main.c更新后,这个目标就需要重新生成。getarg.owriteinfo.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  -omain.o

+++++++ Build Standalone Programe : test +++++++

gcc -Wall -g getarg.o writeinfo.o main.o  -o test

在这次执行的过程中,由于getarg.omain.o目标都依赖getarg.h文件,因此,两个目标的规则都需要重新执行。

执行清除命令clean

$ make clean

执行的结果如下所示:

------- clean ------

rm -f *.o

rm -f test

这里make工具执行的是clean伪目标,删除了目标文件和可执行程序。

知识点:makeMakefile工程管理工具,可以使用隐含规则进行编译,也可以由Makefile编写者制定自己特定的编译规则。

 

 

 

5.3  自动生成Makefile

5.3.1  自动生成Makefile的意义和相关工具

在实际的项目中,由于make规则的复杂性和不确定性,自己编写Makefile是一件费时费力的事情。 Makefile本身具有一定的相似性,因此利用GNU autoconfautomake这两套工具可以协助我们自动产生Makefile文件,并且让开发出来的软件可以像大多数源代码包那样,只需运行命令 “./configure”“make”“make install”就可以把程序安装到系统中,对于各种源代码包的分发和兼容性具有很好的效果。

1autoconf工具介绍

autoconf是一个用于产生可以自动配置源代码包,生成Shell脚本的工具,它可以适应各种类UNIX系统的需要。autoconf产生的配置脚本在运行时独立于autoconf,也就是说使用这些脚本的用户不需要安装autoconf

autoconf生成的配置脚本通常名称是configure,得到这个文件,通常需要以下的依赖文件:

·      configure.in文件:生成configure的必需文件,需要手动编写。

·      aclocal.m4acsite.m4文件:在编写了除autoconf提供的测试外的其他测试补充的时候,才会用到这两个文件,也需要手动编写。

·      acconfig.h文件:如果使用了含有#define指令的头文件,则需要自行编写该文件,一般都需要使用,这个时候会生成另外一个config.h.in文件,这个文件需要和软件包一同发布。

总之,在autoconf运行完毕后,得到两个需要和软件包同时发布的文件:      configureconfig.h.in,当然config.h.in可以不存在。

2automake工具介绍

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)深层模式指的是所有源代码都被存储在子目录中;顶层目录主要包含配置信息。也就是所有源文件及程序员自己写的头文件都位于当前目录的一个子目录中,而当前目录里没有任何源文件。

在这三种支持的目录层次中,平坦模式类型是最简单的,深层模式类型是最复杂的。但是这些模式使用autoconfautomake所遵循的基本原则和流程是一样的。

3.其他工具介绍

1autoheader:能够产生供configure脚本使用的C #define语句模板文件。

2autom4te:对文件执行 GNU M4

3autoreconf:如果有多个 autoconf产生的配置文件,autoreconf可以保存一些相似的工作,它通过重复运行autoconf(以及在合适的地方运行 autoheader)以重新产生autoconf配置脚本和配置头模板,这些文件保存在以当前目录为根的目录树中。

4autoscan:该程序可以用来为软件包创建configure.in文件。autoscan在以命令行参数中指定的目录为根(如果未给定参数,则以当前目录为根)的目录树中检查源文件。为软件包创建一个configure.scan文件,该文件就是configure.in的前身。

5autoupdate:该程序的作用是转换configure.in,从而使用新的宏名。

5.3.2  自动生成Makefile的流程

在进行自动化生成Makefile之前,务必要设定好工作的根目录,在当前环境下,至少要保证autoscanautoconfaclocalautomake这些命令能够正常运行。在这一节中,我们就以一个最简单的示例来说明automakeautoconf的基本使用方法,这个例子是一个平坦模式的模型。自动生成Makefile的流程如图 5-1所示。

5-1  自动生成Makefile的流程

在这里使用标准的sntp客户端源代码进行示例,该源代码只有一个源文件和一个头文件,分别是ntp.hsntp.c

1)首先把所有的源代码文件复制到当前的根目录下,然后运行autoscan扫描所有的代码,并得到autoscan.logconfigure.scan两个文件。

其中autoscan.log是一个空文件,而configure.scan文件则需要用户手动进行编辑,在该例中,修改之前configure.scan的内容应该如下:

#-*- Autoconf -*-

      # Process this file with autoconf toproduce 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.hstdlib.h string.h sys/socket.h sys/time.h unistd.h])

     

      # Checks for typedefs, structures, andcompiler 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 gethostbynamegettimeofday select socket strchr])

      AC_OUTPUT

这是一个标准的config模板,后面针对configure文件的修改都是基于这个文件进行的,configure.scan的基本格式如下:

AC_INIT

    测试程序AC_PROG_CC

    测试函数库(在源文件中没有用到函数库)

    测试头文件AC_HEADER_STDCAC_CHECK_HEADERS

    测试类型定义AC_HEADER_TIME

    测试结构AC_STRUCT_TM

    测试编译器特性

    测试库函数AC_FUNC_ERROR_AT_LINEAC_FUNC_MALLOC  

              AC_FUNC_SELECT_ARGTYPES

    测试系统调用AC_CHECK_FUNCS

    AC_OUTPUT

这个文件中有几个非常重要的语句,一般来说所有的 configure.scan文件都是以AC_INIT开头和以AC_OUTPUT结束的;而且中间的顺序一般不要进行随意的改变,因为通常在本列表中靠后的项目依赖于表中靠前的项目。例如,库函数可能受到typedefs和库的影响。

2configure.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(sntp1.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 toproduce 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.hstdlib.h string.h sys/socket.h sys/time.h unistd.h])

     

      # Checks for typedefs, structures, andcompiler 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 gethostbynamegettimeofday 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

Makefile 是一种用于自动化编译和构建软件的工具。它通常用于编译 C、C++、Java 等程序,但也可以用于其他编程语言。在 Makefile 中,你可以定义目标、规则和变量,使得编译和构建过程更加简单和自动化。下面是 Makefile详细用法: 1. 定义变量 在 Makefile 中,你可以使用变量来存储文件名、目录名、编译器路径等信息。变量的定义格式为 VARIABLE_NAME = variable_value。例如: ``` CC = gcc CFLAGS = -Wall -O2 ``` 其中,CC 和 CFLAGS 分别是编译器和编译选项的变量名,gcc 和 -Wall -O2 分别是变量的值。 2. 定义目标 在 Makefile 中,目标通常是指需要编译的文件或者任务。目标的定义格式为: ``` target: dependencies commands ``` 其中,target 是目标名称,dependencies 是 target 的依赖项,commands 是执行 target 的命令。例如: ``` hello.o: hello.c $(CC) $(CFLAGS) -c hello.c ``` 这个目标将会编译 hello.c 文件,并生成 hello.o 目标文件。 3. 定义伪目标 有些目标并不会生成任何文件,只是用来执行一些任务,比如清理编译生成的文件。这种目标叫做伪目标。在 Makefile 中,你可以使用 .PHONY 关键字来定义伪目标。例如: ``` .PHONY: clean clean: rm -f *.o ``` 这个伪目标将会删除所有的 .o 文件。 4. 定义规则 在 Makefile 中,规则通常是指一组命令,用于完成一些特定的任务,比如编译、链接、打包等。规则的定义格式为: ``` rule_name: dependencies commands ``` 其中,rule_name 是规则名称,dependencies 是该规则的依赖项,commands 是执行该规则的命令。例如: ``` tarball: $(OBJS) tar cvf myprog.tar $(OBJS) ``` 这个规则将会打包所有的目标文件到 myprog.tar 文件中。 5. 定义通配符 通配符可以用来匹配文件名,比如 *.c 表示所有以 .c 结尾的文件。在 Makefile 中,你可以使用通配符来定义变量和目标。例如: ``` SRCS = $(wildcard *.c) OBJS = $(patsubst %.c,%.o,$(SRCS)) ``` 其中,$(wildcard *.c) 会把当前目录下所有的 .c 文件名存储到 SRCS 变量中,$(patsubst %.c,%.o,$(SRCS)) 会把 SRCS 中的 .c 文件替换成 .o 文件,并存储到 OBJS 变量中。 6. 定义条件语句 在 Makefile 中,你可以使用条件语句来根据不同的条件执行不同的命令。条件语句的格式为: ``` ifeq (condition1, condition2) commands else commands endif ``` 其中,condition1 和 condition2 是两个条件表达式,commands 是根据条件执行的命令。例如: ``` ifeq ($(DEBUG),yes) CFLAGS += -g else CFLAGS += -O2 endif ``` 这个条件语句会根据 DEBUG 变量的值来确定编译选项。 7. 定义函数 在 Makefile 中,你可以使用函数来处理变量和命令。函数的格式为: ``` $(function arguments) ``` 其中,function 是函数名,arguments 是函数的参数。例如: ``` OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) all: $(call print_msg,Compiling...) $(OBJS) $(CC) $(CFLAGS) -o myprog $(OBJS) print_msg = echo $1 ``` 这个 Makefile 定义了一个 print_msg 函数,用于输出一条编译信息。all 目标会先执行 print_msg 函数,然后编译所有的 .c 文件。 以上是 Makefile详细用法,通过定义变量、目标、伪目标、规则、通配符、条件语句和函数,你可以更加自动化地编译和构建软件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值