GNU make

来源:互联网 发布:unix高级编程第三版 编辑:程序博客网 时间:2024/05/01 12:52

GNU make


Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
1、显式规则。
显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2、隐晦规则。
由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
3、变量的定义。
在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4、文件指示。
其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。 如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如: -include <filename> -也可以应用到其他操作。

5、注释。
Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。
最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。


Make的工作方式

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)
1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。


Makefile的规则
target ... : prerequisites ...
command
...

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
prerequisites就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令。(任意的Shell命令)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

例子:
edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.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
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
    这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

makefile使用变量:

make会自动推导(如从一个目标.o文件推导出相应的.c文件),这种方法,也就是make的“隐晦规则”

objects = main.o kbd.o command.o display.o /       //定义objects变量来表示所有[.o]文件
insert.o search.o files.o utils.o
edit : $(objects)         //使用objects变量
cc -o edit $(objects)
$(objects) : defs.h         //使用自动推导省去[.c]依赖文件的手动书写
kbd.o command.o files.o : command.h    //手动添加额外的文件依赖项
display.o insert.o search.o files.o : buffer.h
.PHONY : clean       //利用.PHONY定义僞目标
clean :        //make clean
rm edit $(objects)


通配符:“*”,“?”,“[...]”
VPATH 和 vpath
VPATH是特殊的变量,知道文件依赖搜索的目录。VPATH = src:../headers
vpath是一个关键字,用来设置搜索目录,支持模式匹配查找:
vapth使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录。例如:
vpath %.h ../headers
该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
vpath %.c foo:bar
vpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。

多目标与自动化变量$@,$<

$@:表示目标集
$<:表示所有的依赖目标集


静态模式:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>

例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

展开后等价于:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o


自动生成依赖项:

大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。


操作符“:= ”:
避免变量的嵌套查找。这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。

操作符“?=”:
FOO ?= bar
其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif

变量高级用法:
第一种是变量值的替换。我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

例子:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

条件判断:

条件表达式的语法为:
<conditional-directive>
<text-if-true>
endif
以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

<conditional-directive>表示条件关键字,有“ifeq”,“ifneq”,“ifdef”,“ifndef”


使用函数:
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(<function> <arguments>)
或是
${<function> <arguments>}
这里,<function>就是函数名,make支持的函数不多。<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。

字符串处理函数:
字符串替换函数:$(subst <from>,<to>,<text>)
模式字符替换函数:$(patsubst <pattern>,<replacement>,<text>)
去掉空格:$(strip <string>)
查找字符串函数:$(findstring <find>,<in>)
过滤函数:$(filter <pattern...>,<text>)
反过滤函数:$(filter-out <pattern...>,<text>)
排序函数:$(sort <list>)
取单词函数:$(word <n>,<text>)
取单词串函数:$(wordlist <s>,<e>,<text>)
单词个数统计函数:$(words <text>)
首单词函数:$(firstword <text>)

文件名操作函数:
取目录函数:$(dir <names...>)
取文件函数:$(notdir <names...>)
取后缀函数:$(suffix <names...>)
取前缀函数:$(basename <names...>)
加后缀函数:$(addsuffix <suffix>,<names...>)
加前缀函数:$(addprefix <prefix>,<names...>)
连接函数:$(join <list1>,<list2>)
循环函数:$(foreach <var>,<list>,<text>)
call函数:$(call <expression>,<parm1>,<parm2>,<parm3>...)
shell函数:例子 contents := $(shell cat foo)

make控制函数: