Makefile基础
软件构建过程
[源文件] =编译=> [中间代码文件.o] =链接=> [执行文件]
Makefile基础
Makefile里面有什么
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中使用“#”字符,可以用反斜框进行转义,如:“#”。
最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。
Makefile规则
target ... : prerequisites ...
command
...
#==========说明==========#
#target:目标文件。可以是Object File(.o文件),也可以是执行文件。还可以是一个标签(Label)
#prerequisites:要生成那个target所需要的文件或是目标。头文件(.h)或源文件(.c/.cpp)或Object File(.o文件)
#command:make需要执行的命令。(任意的Shell命令)
示例
注意:在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个[Tab]键作为开头。
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的易读。
- 可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。
- 如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。
- clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
make的工作方式
GNU的make工作时的执行步骤入下:
1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
make默认会在当前目录下找名字叫“Makefile”或“makefile”的文件。可用make -f [Makefile文件] 来指定特定Makefile文件。
对于上面的例子,
1、首先找到“edit”这个文件,并把这个文件作为最终的目标文件。
2、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
3、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。
4、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。
Makefile中使用变量
在Makefile中使用变量,可以使Makefile更易维护,不会因为某个地方忘记修改而导致编译失败。
例如,用object变量表示obj文件:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
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 $(objects)
让make自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令。
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们新的makefile又出炉了。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
#“.PHONY”表示,clean是个伪目标文件。
.PHONY : clean
clean :
rm edit $(objects)
另类风格的makefile
make自动推导命令,还可以将[.o]和[.h]的依赖收拢起来:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
清空目标文件的规则
每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。一般的风格都是:
clean:
rm edit $(objects)
更为稳健的做法是:
.PHONY : clean
clean :
-rm edit $(objects)
#rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。
clean的规则不要放在文件的开头,不然,这就会变成make的默认目标。不成文的规矩是——“clean从来都是放在文件的最后”。
引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含进来,include的语法是:
include <filename>;
#filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)
在include前面可以有一些空字符,但是绝不能是[Tab]键开始。
make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make寻找过程是:
1. 在当前目录下首先寻找
2. 如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
3. 如果目录<prefix>;/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。
如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:
-include <filename>;
#其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。
书写规则
指定文件搜索路径
Makefile文件中,VPATH(全大写)特殊变量可以指定make的对依赖文件的搜索路径,如:
VPATH = src;../headers
#make会按当前目录、src、.../headers的顺序依次寻找依赖文件。
另外,还可以使用make的vpath(全小写)关键字指定搜索目录。
1、vpath <pattern>; <directories>;
为符合模式<pattern>;的文件指定搜索目录<directories>;。
2、vpath <pattern>;
清除符合模式<pattern>;的文件的搜索目录。
3、vpath
清除所有已被设置好了的文件搜索目录。
如:
vpath %.h ../headers
#要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
vpath %.c foo:bar
vpath % blish
#表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
多目标
当多个目标同时依赖于一个文件,并且其生成的命令大体类似时,我们可以使用一个自动化变量“$@”,这个变量表示着目前规则中所有的目标的集合。
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) >; $@
#上述规则等价于:
bigoutput : text.g
generate text.g -big >; bigoutput
littleoutput : text.g
generate text.g -little >; littleoutput
#其中,-$(subst output,,$@)中的“$”表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。
静态模式
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。
<targets ...>;: <target-pattern>;: <prereq-patterns ...>;
<commands>;
...
#targets:一系列的目标文件,可以有通配符。是目标的一个集合。
#target-parrtern:targets的模式,也就是的目标集模式。
#prereq-parrterns:目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
举个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
#上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。
#自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.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
使用filter函数:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
# $(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。
自动生成依赖性
在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include “defs.h””,那么我们的依赖关系应该是:
main.o : main.c defs.h
但如果c文件包含多个文件时,则难以维护,所以我们要使用大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
cc -M main.c
其输出是:
main.o : main.c defs.h
注意:GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。
GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。
产生[.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命令做了一个替换。
#第四行就是删除临时文件。
总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:
main.o : main.c defs.h
#转成:
main.o main.d : main.c defs.h
当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完整的依赖规则。再使用Makefile的“include”命令,引入到Makefile文件中。
sources = foo.c bar.c
include $(sources:.c=.d)
#“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d]