Aug 6, 2014
参考文献提供了非常详细的描述,这里整理了一些主要内容。
在unix环境下编译一个工程,了解makefile文件是非常必要的。Makefile文件描述一个工程的编译、链接规则,包括编译哪些文件以及如何编译,如何生成库,如何最后生成最后的可执行文件等。
通常会写一个batch文件,make命令会自动寻找当前目录下的名为“makefile”的文件,一个makefile文件可以include其他的文件。
编译和链接规则:
1) 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2) 如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3) 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
Makefile文件定义依赖关系的规则:
1) 需要创建的目标体(target),通常是目标文件或可执行文件。
2) 要创建的目标体所依赖的文件(prerequisites)。
3) 创建每个目标体时需要运行的命令(command)。
格式:
target : prerequisites
<tab>command
注意command要以tab键开始,如果一行太长,可以使用“/”换行符。
比如一个工程有4个c文件,三个h文件,他的Makefile文件是这样的:
edit : main.o command.o display.o insert.o
cc -o edit main.o command.o display.o insert.o
main.o : main.c defs.h
cc -c main.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
clean :
rm edit main.o kbd.o command.o display.oinsert.o
执行make命令时,make会查找当前的makefile文件,然后将edit作为最终的目标文件,根据依赖关系执行其中定义的command命令,直到得到最终的目标文件。
Note:make命令会比较target和prerequisities文件的修改日期,如果后者日期更新或target不存在,就会执行后面定义的command命令重新生成target。
上面makefile文件中最后的clean,是重新编译用到的,显式执行make clean才会执行clean下面定义的command。每个文件都应该包含一个清空目标文件的规则,不过更稳健的写法是:
.PHONY : clean
clean:
-rm edit main.o kbd.o command.o display.oinsert.o
.PHONY 表示后面是一个伪目标,rm前面的“-”则表示,中间也许会出错,但是不要管它,继续执行。
1 Makefile文件包含的五个东西
1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。
2 Makefile文件中的隐晦规则
Make命令可以自动推导target所需要的命令,对于一个.otarget 也可以自动将同名的.c文件加到依赖关系中,所以这些内容可以省略掉。
3 变量
Makefile文件中可以定义变量,后面就可以引用这些变量,如
objects = main.o kbd.o command.o display.o insert.o
edit : $(objects)
变量在声明时赋予初值,引用时使用“$”符号后跟“()”或“{}”将变量名括起来。变量会在被引用的地方展开,就像宏一样。
变量可以用在很多地方,如目标、依赖文件、命令甚至新的变量中,如下面:
pow_include = a.h b.h
mode_include = c.h d.h
Include_dirs = $(pow_include) $(mode_include)
4 引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:
include<filename>
include不能以tab键开始,filename可以包含路径和通配符。
如果文件都没有指定绝对路径或是相对路径的话,
make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个
目录下找:
1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指
定的目录下去寻找。
2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,m
ake也会去找。
5 环境变量MAKEFILES
如果环境中定义了环境变量MAKEFILES,make会把这个变量的值当作一个类似include的动作,只是这些makefile中的target不会起作用。如果其中的makefile出错,make也会不理。定义了这个环境变量后,所有的makefile都会受到影响。
6 Make命令的执行步骤
make工作时的执行步骤:
1、 读入所有的Makefile。(makefile或Makefile)
2、 读入被include的其它Makefile。
3、 初始化文件中的变量
4、 推导隐晦规则,并分析所有规则
5、 为所有的目标文件创建依赖关系链
6、 根据依赖关系,决定哪些目标要重新生成
7、 执行生成命令
在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。(target一般是一个文件,但也可能会是多个文件,以空格分开)。
7 makefile文件的文件名
大多数的make都支持Makefile或makefile这样的文件名,如果要制定特定的文件名,可以使用那个-f或--file选项: make -f build.mk
8 文件搜索
当文件位于多个不同目录时,为了避免在每个文件前添加路径,可以把路径告诉make,让make自动去找。Makefile文件中的”VPATH”就是做这个事情,如果定义了该变量,make在当前目录查找不到,就会在vpath中指定的路径进行查找。
9 添加依赖文件的路径
编译一个lib,依赖其他模块的内容,添加头文件的路径时,如果此头文件还需要其他的头文件需要一起将这些依赖的文件的路径也加进来。
VPATH =../inc:../mod/inc
指定了两个路径,用冒号分隔。
此外,可以在make命令中使用vpath关键字,它更为灵活,可以指定不同的文件在不同的目录中搜索。
10 自动生成依赖性
大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
cc -M main.c
如果main.c中定义了”#include def.h”,则输出为
main.o : main.c def.h
如果使用GNU的编译器,需要使用-MM参数,否则会把标准库的头文件也加进来。
Make与这些依赖关系工作的方式是,GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。
于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。
这里,我们给出了一个模式规则来产生[.d]文件:
%.d: %.c
@set -e; rm -f $@;
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; /
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.$$$$> $@; /
rm -f $@.$$$$
这个规则的意思是,所有的[.d]文件依赖于[.c]文件,“rm -f $@”的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“$<”,也就是[.c]文件生成依赖文件,“$@”表示模式“%.d”文件,如果有一个C文件是name.c,那么“%”就是“name”,“$$$$”意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:
main.o : main.c defs.h
转成:
main.o main.d : main.c defs.h
于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件,例如:
sources = foo.c bar.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标。
11 书写命令
每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。
11.1 显示命令
通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译XXX模块......
这时echo 本身不会显示出来,只会显示后面的“正在编译XXX模块......”
make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。而make参数“-s”或“--slient”则是全面禁止命令的显示。
11.2 命令出错
每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令。
为了忽略命令的出错,可以在Makefile的命令行前加一个减号“-”(如mkdir命令可能由于路径已经存在而执行失败),标记为不管命令出不出错都认为是成功的。
一个全局的办法是,给make加上“-i”或是“--ignore-errors”参数。
参考:
Linux makefile 教程 非常详细,且易懂.
http://blog.csdn.net/liang13664759/article/details/1771246