学习书籍原地址《跟我一起写Makefile》,感谢原作者的付出!
书写规则
规则包含两个部分,依赖关系和生成目标的方法。
Makefile中应只有一个最终目标,也就是第一条规则中的第一个目标。
一、规则的语法
targets ... : prerequisites ...
[TAB] command
...
或:
targets ... : prerequisites ; command
[TAB] command
...
-
targets是文件名,以空格分开,可以使用通配符,可以是多个文件。
-
command是命令行,如果单独作为一行,必须以[TAB]键开头,如果在prerequisite后用逗号分隔。
-
prerequisite是依赖文件或目标。
如果命令太长,可以使用续行符“\”,在一行内make没有字符上限。
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
二、在规则中使用通配符
make支持“*”,“?”,“[…]”三种通配符这和UNIX的B-Shell相同。
波浪号“~”可以表示:
3. 当前用户的home目录;
4. 某用户的宿主目录,如~yzk/Document表示用户yzk的宿主目录下的Document文件夹。
make也支持“~”。而在Windows或是MS-DOS下,用户没有宿主目录,则波浪号所指的位置随环境变量“HOME”的改变而改变。
注意:在变量中使用*时,如果是这样的写法:
objects = *.o
*.o不会被展开!!!,objects的值就是*.o
如果要包含所有.o文件,可以这样写:
objects := $(wildcard *.o)
:=的意思是覆盖之前的变量
wildcard关键字的作用就是扩展通配符。它是Makefile中的关键字。
四、文件搜寻
可以将源文件分类并存放在不同的目录中,然后在Makefile中文件前加上路径,告诉make该去哪里找。
Makefile文件中有个特殊变量“VPATH”,它可以指定文件存放的位置,如果没有指明这个变量,make只会在当前目录下寻找。
VPATH = src:../headers
路径之间用冒号分隔。
当然,如果make在当前目录下找不到,才会去VPATH中指定的目录寻找
另一种方法是使用“vpath”关键字(全小写),它可以指定不同的文件在不同的目录中搜索。它的使用方法有三种:
vpath <pattern> <dierctories>
为符合模式<pattern>的文件指定搜索目录<directories>vpath <pattern>
清除符合模式<pattern>的文件的搜索目录vpath
清除所有已被设置好了的文件搜索目录
vpath 使用方法中的<pattern>可以包含“%”字符,它代表匹配零或若干字符,如“%.h”表示所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)。
可以连续使用vpath关键字,指定不同的搜索策略,make会按照vpath的先后顺序来执行搜索。
vpath %.c foo
vpath % blish
vpath %.c bar
其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
vpath %.c foo:bar
vpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
—该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 四、文件搜寻
VPATH和vpath的区别在于,VPATH是寻找目录下的所有文件,而vpath增加了限制条件,只在一些文件中进行寻找。相同点在于,make都会优先寻找当前目录。
五、伪目标
之前的“clean”目标就是一个“伪目标”。
clean :
rm *.o edit
使用make clean可以执行这个伪目标的命令,但是不会生成目标文件,因为其没有依赖。伪目标取名不能和文件名重名。
为了避免和文件重名的情况,我们可以使用一个特殊的标记“.PHONY”来显式指明这个目标是伪目标,让make知道不管有没有这个文件,这个目标就是伪目标。
.PHONY : clean
只要有这个声明,不管有没有叫“clean”的文件,只有使用“make clean”才能运行“clean”目标。
.PHONY : clean
clean:
rm *.o edit
伪目标同样可以作为默认目标/最终目标,只要放在第一个。且伪目标可以有依赖文件。
如果Makefile需要生成多个可执行文件,又只想敲一个make了事,且所有的目标文件都写在一个Makefile中,那么可以使用伪目标:
all : test1 test2 test3
.PHONY : all
test1 : test1.o
g++ -o test1 test1.o
test1.o : test1.cpp
g++ -c test1.cpp
test2 : test2.o
g++ -o test2 test2.o
test2.o : test2.cpp
g++ -c test2.cpp
test3 : test3.o
g++ -o test3 test3.o
test3.o : test3.cpp
g++ -c test3.cpp
执行make则会生成 test1,test2,test3三个可执行文件。
伪目标总是被执行,所以其依赖总是不如伪目标新,所以每次都会被生成。
伪目标亦可以成为其他目标的依赖。
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
#该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 五、伪目标
“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。
六、多目标
Makefile的规则支持多目标,使用自动化变量“$@”表示目前规则中所有的目标的集合。
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
#该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 六、多目标
subst是一个Makefile函数,作用是截取字符串,“$@”表示目标的集合,类似数组,从"$@"中依次取出目标并在命令中使用。
七、静态模式
静态模式可以更加容易地定义多目标的规则,可以让规则变得更加的有弹性和灵活。
语法:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
targets是目标的一个集合,可以有通配符;
target-pattern指明targets的模式,也就是目标集模式;
prereq–patterns是目标的依赖模式,它对target-pattern形成的模式在进行依次依赖目标的定义。
如果定义成“%.o”,意思就是在中都是以".o"结尾的文件;
如果定义成"%.c",意思就是对所形成的目标集进行二次定义,计算方法是,取中的“%”(即舍去后缀),并加上中的后缀,行成新的集合(%.c)。
例如:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
#该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 七、静态模式
上面的例子指明目标从$(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
#该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 七、静态模式
试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可
以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一
个很强大的功能。再看一个例子:
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 $<
#该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 七、静态模式
$(filter %.o,$(files))表示调用 Makefile 的 filter 函数,过滤“$filter”集,只要其中模式为“%.o”的内容。这个例字展示了 Makefil中更大的弹性。
(第七节没怎么懂,学到后面再回来看看)
八、自动生成依赖性
在Makefile中,依赖关系可能会包含一系列的头文件,依赖关系多了,维护就很麻烦。为了避免这种情况发生我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,可以自动寻找源文件包含的头文件并生成依赖关系。
如果是GNU的C/C++编译器,建议使用“-MM”,不然会把标准库头文件也包含进来。
GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“.c”文件都生成一个“.d”的Makefile文件,用于存放.c文件的依赖关系。
这里给出了一个模式规则来产生“.d”文件:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
#该模式规则摘自 陈皓《跟我一起写Makefile》第五章 书写规则 八、自动生成依赖性
所有的".d"文件依赖于".c"文件,“rm -f $@”的意思是删除所有的目标,也就是".d"文件;
第二行为每个依赖文件“$<”,也就是“.c”文件生成依赖文件,“$@”表示模式“%.d”文件,如果又一个文件是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)
#该示例摘自 陈皓《跟我一起写Makefile》第五章 书写规则 八、自动生成依赖性
“.c=.d”的意思是做一个替换,把变量中所有的“.c”都换成".d"。最先载入的".d"文件中的目标会成为默认目标。