首页 > 代码库 > Makefile 7——自动生成依赖关系 三颗星

Makefile 7——自动生成依赖关系 三颗星

后面会介绍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) $@
25 $(EXE):$(OBJS)
26     $(CC) -o $@ $^
27 $(DIR_OBJS)/%.o:%.c 
28     $(CC) -o $@ -c $^
29 $(DIR_DEPS)/%.dep:%.c
30     @echo "Creating $@ ..."
31     @set -e;32     $(RM) $(RMFLAGS) $@.tmp;33     $(CC) -E -MM $^ >$@.tmp;34     sed s,\(.*\)\.o[:]*,objs/\1.o:,g <$@.tmp >$@;35     $(RM) $(RMFLAGS) $@.tmp
36 clean:
37     $(RM) $(RMFLAGS) $(DIRS) 

(这个Makefile废了不少力气才想明白。。。)

和之前的complicated项目的Makefile相比:

1,增加了deps文件夹

2,删除了目标文件创建规则中的foo.h依赖,并将规则中的$<变回了$^

3,增加了了DEPS变量用于存放文件

4,为all目标增加了$(DEPS)

5,增加了一个用于创建依赖关系问价你的规则。在这个规则中,使用了gcc的-E和-MM选项来获取依赖关系。在生成最终的依赖关系文件之前,使用了一个由$@.tmp表示的临时文件,且在依赖文件生成以后将其删除。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‘ <$@.tmp >$@;
这个语句才是最难的,也是最费力的。

Makefile 7——自动生成依赖关系 三颗星