后面会介绍gcc获得源文件依赖的方法,gcc这个功能就是为make而存在的。我们采用gcc的-MM选项结合sed命令。使用sed进行替换的目的是为了在目标名前加上“objs/”前缀。gcc的-E选项,预处理。在生成依赖关系时,其实并不需要gcc编译源文件,只要预处理就可以获得依赖关系了。通过-E选项,可以避免生成依赖关系时gcc发出警告,以及提高依赖关系的生成效率。
现在,已经找到自动生成依赖关系的方法了,那么如何将其整合到我们complicated项目的Makefile中呢?自动生成的依赖信息不能直接出现在Makefile中,因为不能动态地改变Makefile中的内容,此时我们需要通过创建依赖关系文件的方式。假设依赖关系的文件以“.dep”结尾,因此我们新创建一个deps文件,用来存放依赖关系文件信息。
Makefile如下:
1 .PHONY: all clean 2 3 MKDIR = mkdir 4 RM = rm 5 RMFLAGS = -rf 6 7 CC=gcc 8 9 DIR_OBJS=objs 10 DIR_EXES=exes 11 DIR_DEPS=deps 12 13 DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) 14 EXE=complicated 15 EXE:=$(addprefix $(DIR_EXES)/,$(EXE)) 16 SRCS=$(wildcard *.c) 17 OBJS=$(SRCS:.c=.o) 18 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS)) 19 DEPS=$(SRCS:.c=.dep) 20 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS)) 21 22 all:$(DIRS) $(DEPS) $(EXE) 23 $(DIRS): 24 $(MKDIR) [email protected] 25 $(EXE):$(OBJS) 26 $(CC) -o [email protected] $^ 27 $(DIR_OBJS)/%.o:%.c 28 $(CC) -o [email protected] -c $^ 29 $(DIR_DEPS)/%.dep:%.c 30 @echo "Creating [email protected] ..." 31 @set -e;32 $(RM) $(RMFLAGS) [email protected];33 $(CC) -E -MM $^ >[email protected];34 sed ‘s,\(.*\)\.o[:]*,objs/\1.o:,g‘ <[email protected] >[email protected];35 $(RM) $(RMFLAGS) [email protected] 36 clean: 37 $(RM) $(RMFLAGS) $(DIRS)
(这个Makefile废了不少力气才想明白。。。)
和之前的complicated项目的Makefile相比:
1,增加了deps文件夹
2,删除了目标文件创建规则中的foo.h依赖,并将规则中的$<变回了$^
3,增加了了DEPS变量用于存放文件
4,为all目标增加了$(DEPS)
5,增加了一个用于创建依赖关系问价你的规则。在这个规则中,使用了gcc的-E和-MM选项来获取依赖关系。在生成最终的依赖关系文件之前,使用了一个由[email protected]表示的临时文件,且在依赖文件生成以后将其删除。set -e的作用是告诉shell,在生成依赖关系文件的过程中如果出现任何错误就直接退出。shell异常退出的最终表现就是make会告诉我们出错了,从而停止后续的make工作。如果不设置这一行,当构建依赖出错时,make还会继续后面的工作并最终出错,这并不是我们希望看到的。读者可以测试故意在源文件或者头文件中植入错误并去掉set -e选项观察make的行为和加上set -e有上面不同。
这里还有几个知识点需要补充。
1.对于规则中的每一条命令,make都是在一个新的shell上运行它的。
2.如果希望多个命令在同一个shell中运行,可以用“;”将这些命令连起来。
3.当命令很长时,可以用“\”将一个命令书写成多行。
为了更好的理解第一点,我们做一个实验。现假设需要创建一个test目录,然后在这个test目录下再创建一个subtest子目录。编写Makefile如下:
1 .PHONY:all 2 all: 3 @mkdir test 4 @cd test 5 @mkdir subtest
可以看到test和subtest是同级目录并非父子目录,然后用上面提到的知识点更改Makefile:
1 .PHONY:all 2 all: 3 @mkdir test;4 cd test;5 mkdir subtest
这样就可以达到目的了。不过你可能会想,为什么这里后面的cd和最后一个mkdir不需要在前面加上@呢?那么我们加上试试呢?
如果使用了分号“ ;”,表示命令在同一个shell中运行,而且使用“ \”链接一条命令,既然是一条命令,自然不能够识别后面的@cd或者@mkdir,因为最开始的mkdir使用@,让终端不显示执行的指令,后面的cd和mkdir是在前面操作的情况下进行的 ,此时,直接使用命令即可。
还有一个需要注意的地方:
如同
EXE=complicated
EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
这样的Makefile,为什么第二个赋值我们是用:=而不是直接=呢?这也是需要注意的小细节,这个在之前的随笔中已经说过,要是用=,会导致无限递归,为什么呢?因为EXE在复制号左边,而右边又有$(EXE)(EXE的引用),这样会无限调用,make报错。不信你可以试试。
最后,来到最难的一个东西:
sed ‘s,\(.*\)\.o[:]*,objs/\1.o:,g‘ <[email protected] >[email protected];
这个语句才是最难的,也是最费力的。