说明:版权所有归作者,只供学习交流,若有其它用途请联系作者,转载请遵守IT人职业规范,请注明转载地址
一, Makefile介绍
随着软件复杂度的提高,人们提出了模块化的概念,即将复杂的软件分解为很多细小的功能模块,由很多人分工合作完成。于是软件代码按功能模块分散到了各个不同的文件中。虽然软件的模块化极大地减少了软件开发的难度和复杂度,却使得编译这些文件成为了问题。有时候只是改动了某个文件却不得不重新编译整个工程。,重新编译所有的代码往往需要很长的时间。
对于大型项目而言使用GNUMake对源代码进行管理后,Make将只编译改动的代码文件,而不用完全编译。Makefile带来的好处就是“自动化编译”,一旦写好,只需要一make命令,整个工程完全自动编译,极大的提高了软件开发的效率。其中make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake, Linux 下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
Make通过读入配置好的文本文件,并根据文本文件中预先定义的规则和步骤,完成代码的编译和链接工作,最终生成所需要的项目文件。这个文件在缺省的情况下文件名为GNUmakefile、makefile或Makefile,然后使用make命令进行自动编译。Makefile需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要需要重新编译所有依赖该文件的源文件。使用make进行自动编译时,通常会做的工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改,则呈现编译所有该头文件的源文件。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。
Make在对项目文件进行编译时,会判断文件的修改和生成时间。如果某源码文件在上次编译后再次被修改,则Make将只编译该文件,而不会对整个项目进行重新编译。Make还可以完成清除项目编译过程中所生成的中间文件等很多功能。
二,Make支持的命令选项
-b或-m | 忽略兼容性 |
-B或--always-make | 无条件编译所有目标 |
-d | 打印调试信息 |
-e或--environment-overrides | 在makefile中不替换环境变量的值 |
-f FILE或--file=FILE或--makefile=FILE | 指定FILE文件为makefile |
-h或--help | 显示make命令帮助信息 |
-i或--ignore-errors | 忽略命令中的错误信息 |
-I DIRECTORY或 --include-dir=DIRECTORY | 搜索指定目录中的makefile |
-k或--keeping-going | 即使某些目标无法编译,仍然继续编译工作 |
-o FILE, --old-file=FILE或 --asume-old=FILE | 即使这些文件已经做过修改但仍然不编译该文件 |
-p或--print-data-base | 打印make的内部数据信息 |
-r或--no-builtin-rules | 禁止编译中的隐式规则 |
-s或--silent或--quiet | 不显示编译中的命令 |
-v或--version | 显示make命令的版本号 |
三,makefile的语法
makefile有着严格的语法规定,makefile的内容大致可以分为两大部分(严格来说应该包括5个部分:显示规则、隐式规则、变量定义、指令和注释等)。
(1):变量定义:和C语言类似,这些变量是区分大小写的。可以使用变量定义来指定具体的编译器参数,或保存要生成的文件列表。(使用变量保存要编译的文件列表,可以避免添加新的文件到工程时修改整个makefile文件,而只要修改该变量即可)
与C/C++不同,在makefile中定义变量是不用先声明的。在定义完变量后,如果要引用该变量,可以使用$(变量名)的形式访问改变量。
为了加深理解,我们举个例子:
CC=gcc (定义变量)
hello : hello.c
$(CC)–o hello hello.c
完成编辑后,将其保存为名为makefile的文件。在Shell命令下输入make命令,将完成hello.c的编译。
除了可以在makefile文件中定义变量外,还可以在makefile中使用一些预先设定的变量,如下表所示:
makefile支持的预定义变量及其意义
变量名 | 含义 |
$@ | 表示当前规则中的目标文件名 |
$? | 新修改过的的依赖文件列表 |
$* | 不包含扩展名的目标文件名 |
$< | 当前规则中的第一个依赖文件名 |
$% | 当目标文件为库文件时,该变量为库文件名;如果不是库文件,该变量为空值 |
$^ | 所有的依赖文件,以空格分开,不包含重复的依赖文件。 |
AR | 归档程序名称,默认为ar |
ARFLAGS | 归档程序选项 |
CC | C编译器命令名,默认为cc |
CFLAGS | C编译器编译参数 |
(2):基于依赖关系的指令:指明要编译成功某个文件,需要编译的代码文件。
前面的例子中我们看到过下面的例子
hello : hello.c
gcc–o hello hello.c
第一行hello被称为目标,而冒号后的hello.c为依赖文件。也就是说生成目标hello依赖于冒号后的hello.c文件。在makefile中将这种形式的语句称为规则,规则的具体规定如下。
目标(可能存在多个目标)…:依赖文件(可能存在多个条件)…
<TAB>命令(在makefile中,TAB后的命令将交给Shell来解释)
目标通常可以取为要生成的文件名称。依赖条件是用来输入从而生成目标的文件,一个目标可以有几个依赖。在熟悉makefile规则后,将前面的makefile做如下的改进:
CC=gcc
CFLAGS=-o
hello(目标) : hello.c(依赖)
$(CC)$(CFLAGS) $@ hello.c(命令)
Clean :
rf hello
其中使用了“@”变量。“@”表示当前目标名,这里目标名为hello。这样编译出的可执行文件为hello。
值得一提的是,目标还可以是某个动作。如果常使用make编译程序,一定注意在编译后,可以使用make clean将中间生成的文件做必要的清理。这时,目标就没有依赖关系了。
可能有些人会问,为什么clean动作在make的时候没有被执行?
在正常的情况下,如果没有告诉make执行clean命令,clean将永远不被执行。这种没有任何依赖的规则、仅仅表达动作的目标称为假想目标或伪目标。
再来看一个例子:
hello: main.o func1.o func2.o
@gcc main.o func1.o func2.o -o hello #用“@“取消编译时的回显
main.o : main.c
gcc –c main.c
func1.o : func1.c
gcc –c func1.c
func2.o : func2.c
gcc –c func2.c
.PHONY : clean #“. PHONY“将“clean”目标声明为伪目标
clean :
rm –f hello main.o func1.o func2.o
注意:“#“后面的位注释。
四,makefile的隐含规则
隐含规则可以认为是默认的约定或惯例。Make在makefile中没有指明具体处理规则时会采用惯例来处理。例如,将后缀为.c的文件编译为.o文件。
隐含规则会使用一些预定义的变量,这样可以通过改变这些预定义的变量的值来定制使用隐含规则编译的具体参数。例如,改变CFLAGS变量可以控制编译的过程。如果不想隐含规则使用预定义的变量,可以使用选项R或no-builtin-varibles来删除所有的隐含规则的变量。
下面列出一些常用的隐含规则,如果需要获得隐含规则的详细信息,可以查阅相关的GNU Make文档。
规则 | 说明 |
C程序编译 | .o文件自动由同名的.c文件生成,编译命令为: $(CC) –c $(CPPFLAGS) $(CFLAGS) |
C++程序编译 | .o文件自动由同名的.cc或同名的.c文件生成,编译命令为: $(CXX) –c $(CPPFLAGS) $(CFLAGS) 也就是说如果要让make对C++代码执行隐含规则,C++代码的后缀名必须为.cc或.c
|
Pascal程序编译 | .o文件自动由同名的.p生成,编译命令为:$(PC) –c $(PFLAGS) |