Makefile里有什么
Makefile中主要包含五个东西:显示规则,隐晦规则,变量定义,文件指示和注释
显示规则
:说明了如何生成一个或多个目标文件,这是由Makefile的书写着明确指示的要生成的文件,需要依赖的文件,生成命令
隐晦规则
:由make自动推导的功能,可以让我们写一些比较粗糙的Makefile文件
变量定义
:在Makefile中我们需要定义一系列的变量,变量一般都是字符串,相当于C中的宏,当Makefile被执行的时候,其中的变量都会被扩展到相应的引用位置上
文件指示
:包括三个部分,一个是Makefile中引用另一个Makefile,就像C中的include
一样;另一个是指根据某些情况,制定Makefile的有效部分,就像C语言中的预编译#if
一样;还有一个就是定义多行命令
注释
:Makefile只有行注释,跟shell脚本一样,使用关键字符#
,如果我们需要使用#字符,通过反斜杠()进行转义,如\#
Makefile的文件名
在默认情况下,make命令会在当前目录下顺序查找GNUmakefile
,makefile
,Makefile
的文件,找到了解析这些文件。当然我们可以使用别的文件名来书写Makefile,例如Make.Linux
,Make.Solaris
,Make.AIX
等,如果要制定特定的Makefile,我们可以使用make的-f
或者-file
参数,例如make -f Make.Linux
或者make -file Make AIX
引用其他的Makefile
在Makefile使用include
关键字可以吧别的Makefile
包含进来,有点像C语言中的#include
,被包含的文件会原模原样的放在当前文件包含位置,include语法是:
include <filename>
filename可以是当前操作系统Shell的文件模式,可以包含路径和通配符
在include前面有一些空字符,但却不是tab
键开始。include可以用一个或者多个空格隔开。假如我们有这样几个Makefile文件:a.mk
,b.mk
,c.mk
,还有一个文件叫做foo.make
,以及一个变量$(bar)
,其中包含了e.mk
和f.mk
,那么下面的语句“
bar = e.mk f.mk
include foo.make *.mk $(bar)
等价于
include foo.make a.mk b.mk c.mk e.mk f.mk
在我们使用include
的时候,如果有文件没有找到,make会产生一条警告,但是不会立刻有执行错误,他会继续载入其他的文件,一旦完成makefile的提取,他会重新找那些没有找到的或者没有读取成功的文件,如果还是不行,则会产生一个致命错误,如果我们想要让make不理那些无法读取的文件。而继续执行,我们可以在include
前加上-
,例如
-include <filename>
表示无论include过程中遇到什么错误,都不要报错,继续执行
环境变量MAKEFILES
如果当前环境中定义了环境变量MAKEFILES,那么make会把这个变量中的值做一个类似于include的动作,这个变量中的值是其他的Makefile,用空格隔开。指示他和include不同的是,从这个环境中引入Makefile的目标不会一起作用。
make的工作方式
GNU的make工作时的执行步骤是:
- 读取所有的Makefile
- 读取被
include
的其他Makefile - 初始化文件中的变量
- 推导隐晦规则,并且分析所有规则
- 为所有的目标文件创建依赖关系链
- 根据依赖关系,决定哪些目标要重新生成
- 执行生成命令
Makefile书写规则
规则包含两个部分,一个是依赖关系,一个是生成目标的方法
在Makefile中,规则的顺序很重要,因为Makefile中只应该有一个最终目标,其他的目标都是被这个目标所连带出阿里的,所以要让make知道我们的最终目标是什么。一般情况下,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终目标。如果第一条规则中的目标有很多,那么第一个目标位终极目标,make所完成的也就是这个目标
规则举例
#foo模块
foo.o:foo.c defs.h
gcc -c -g foo.c
从这个例子中我们知道foo.o
是我们的目标,foo.c
和defs.h
是目标所依赖的源文件,而只有一个命令gcc -c -g foo.c
这个规则告诉我们两件事情:
- 文件的依赖关系,
foo.o
依赖于foo.c
和defs.h
文件,如果foo.c
和defs.h
的文件日期要比foo.o
文件日期新,或者foo.o
不存在,那么依赖关系发生 - 生成
foo.o
文件
在规则中使用通配符
如果我们想定义一系列比较类似的文件,我们很自然的就想到使用通配符,make支持三种通配符,*
,?
,[...]
~
在Linux中表示用户根目录。例如~/test
就表示当前用户的$HOME目录下的test目录,而如果是~paul/test
表示的用户paul的宿主目录下的test目录,而在windows下,用户没有宿主目录,那么~
代表的是环境变量中HOME
的位置
*
想要去通过标识符去识别一系列文件的总称,例如*.c
表示所有后缀为.c
的文件。同样这里是可以使用转义符的。
Makefile中的文件搜索
在一些大工程中,我们有大量的源文件,通常需要把这些源文件进行分类,在java中这叫分包,那么也就是放在不同的目录下。所以当make需要去寻找文件的依赖关系时,我们可以在文件前加上路径,最好的方式还是把一个路径告诉make,让make自己去找
Makefile中的特殊变量VPATH
就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去寻找依赖,如果定义了这个变量,make就会在当前目录下去找依赖,如果找不到,再去指定的目录下去找。
VPATH = src:../headers
上面的定义制定了两个目录,src
和../headers
,make会按照这个顺序进行搜索,目录有冒号:
分割
另一个设置文件搜索路径的方法是使用make的vpath
关键字(小写),这里不是变量,与上面的VPATH
类似,但是它更加灵活,他可以指定不同的文件在不同的搜索目录中,三种使用方式
- vpath 为符合模式的文件制定搜索目录
- vpath 清除符合模式的文件搜索目录
- vpath 清除所有已被设置好的文件搜索目录
vpath使用方法中的需要包含%
字符,%
字符的意思是匹配零或者若干字符,例如%.h
表示所有已.h
结尾的文件
vpath %.h ../headers
该语句表示要求make在../headers
目录下搜索所有以.h
结尾的文件
同时我们可以连续的使用vpath
语句,以指定不同搜索策略,如果连续的语句中出现了相同的<pattern>
或者被重复了的<pattern>
,则make会按照先后顺序去执行搜索
vpath %.c foo
vpath % blish
vpath %.c bar
表示.c
结尾的文件,现在foo目录,然后是blish,最后是bar目录
vpath %.c foo:bar
vpath % blish
伪目标
有一个例子,如下
#写在Makefile中的
objects = main.o kdb.o command.o
edit:main.o kdb.o command.o
gcc -o edit $(objects)
main.o:defs.h buffers.h
kdb.o:defs.h
command.o: buffers.h
clean:
rm *.o edit
我们在执行make的时候生成了很多编译文件,我们也应该提供一个清除他们的target–clean。虽然我们的clean是一个target,但是我们并不需要生成文件,所以我们可以使用伪目标的方式。
伪目标不是一个文件,只是一个标签,由于伪目标不是文件,所以make无法生成它的依赖关系和决定是否要执行他。我们只有通过显示的指明这个target才会生效,注意伪目标不能和文件名重名。
为了避免和文件重名,我们可以使用特殊标记.PHONY
来显示的指明这个目标是一个伪目标,向make指明,不管是否有这个文件,这个目标就是伪目标
.PHONY:clean
只要有这个声明,不管是否有clean
文件,要运行clean
这个目标,只有make clean
这样,完整的写法是
.PHONY:clean
clean:
rm *.o edit
伪目标一般没有依赖的文件,但是我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为默认目标,只要将其放在第一个。
all : prog1 prog2 prog3
.PHONY:all
prog1:prog1.o utils.o
gcc -o prog1 $@
prog2:prog2.o buffers.o
gcc -o prog2 $@
prog3:prog3.o kdb.o
gcc -o prog3
grog1.o:grog1.c defs.h buffers.h
gcc -c grog1.c
grog2.o:grog2.c defs.h
gcc -c grop2.c
grog3.o:grog3.c buffers.h
gcc -c grop3.c
多目标
Makefile的规则中的目标可以不止一个,其支持多个目标,有可能我们的多个目标同时依赖一个文件,并且其声称的命令大体类似,。当然多个目标的生成规则的执行命令是同一个,这里我们可能有问题,但是我们可以使用一个自动化变量$@
,这个变量表示目前规则中所有目标的集合。例子看这里
静态模式
静态模式可以更加容易的定义多目标的规则,可以让我们的规则变得更加有弹性和灵活,语法如下
<targets...>:<target-pattern>:<prereq-patterns..>
<commands>
targets
定义了一系列的目标文件,可以有通配符,是一个目标的集合
target-parrtern
指明了targets的模式,也就是的目标集模式
prereq-parrterns
是目标的依赖模式,他对target-parrtern形成的模式在进行一次依赖目标的定义