自动生成依赖

廖雪峰
资深软件开发工程师,业余马拉松选手。

前面我们讲了隐式规则和模式规则,这两种规则都可以解决自动把.c文件编译成.o文件,但都无法解决.c文件依赖.h文件的问题。

因为一个.c文件依赖哪个.h文件必须要分析文件内容才能确定,没有一个简单的文件名映射规则。

但是,要识别出.c文件的头文件依赖,可以用GCC提供的-MM参数:

$ cc -MM main.c
main.o: main.c hello.h

上述输出告诉我们,编译main.o依赖main.chello.h两个文件。

因此,我们可以利用GCC的这个功能,对每个.c文件都生成一个依赖项,通常我们把它保存到.d文件中,再用include引入到Makefile,就相当于自动化完成了每个.c文件的精准依赖。

我们改写上一节的Makefile如下:

# 列出所有 .c 文件:
SRCS = $(wildcard *.c)

# 根据SRCS生成 .o 文件列表:
OBJS = $(SRCS:.c=.o)

# 根据SRCS生成 .d 文件列表:
DEPS = $(SRCS:.c=.d)

TARGET = world.out

# 默认目标:
$(TARGET): $(OBJS)
	$(CC) -o $@ $^

# xyz.d 的规则由 xyz.c 生成:
%.d: %.c
	rm -f $@; \
	$(CC) -MM $< >$@.tmp; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@; \
	rm -f $@.tmp

# 模式规则:
%.o: %.c
	$(CC) -c -o $@ $<

clean:
	rm -rf *.o *.d $(TARGET)

# 引入所有 .d 文件:
include $(DEPS)

变量$(SRCS)通过扫描目录可以确定为hello.c main.c,因此,变量$(OBJS)赋值为hello.o main.o,变量$(DEPS)赋值为hello.d main.d

通过include $(DEPS)我们引入hello.dmain.d文件,但是这两个文件一开始并不存在,不过,make通过模式规则匹配到%.d: %.c,这就给了我们一个机会,在这个模式规则内部,用cc -MM命令外加sed.d文件创建出来。

运行make,首次输出如下:

$ make
Makefile:31: hello.d: No such file or directory
Makefile:31: main.d: No such file or directory
rm -f main.d; \
        cc -MM main.c >main.d.tmp; \
        sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.tmp > main.d; \
        rm -f main.d.tmp
rm -f hello.d; \
        cc -MM hello.c >hello.d.tmp; \
        sed 's,\(hello\)\.o[ :]*,\1.o hello.d : ,g' < hello.d.tmp > hello.d; \
        rm -f hello.d.tmp
cc -c -o hello.o hello.c
cc -c -o main.o main.c
cc -o world.out hello.o main.o

make会提示找不到hello.dmain.d,不过随后自动创建出hello.dmain.dhello.d内容如下:

hello.o hello.d : hello.c

上述规则有两个目标文件,实际上相当于如下两条规则:

hello.o : hello.c
hello.d : hello.c

main.d内容如下:

main.o main.d : main.c hello.h

因此,main.o依赖于main.chello.h,这个依赖关系就和我们手动指定的一致。

改动hello.h,再次运行make,可以触发main.c的编译:

$ make
rm -f main.d; \
        cc -MM main.c >main.d.tmp; \
        sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.tmp > main.d; \
        rm -f main.d.tmp
cc -c -o main.o main.c
cc -o world.out hello.o main.o

在实际项目中,对每个.c文件都可以生成一个对应的.d文件表示依赖关系,再通过include引入到Makefile,同时又能让make自动更新.d文件,有点蛋生鸡和鸡生蛋的关系,不过,这种机制能正常工作,除了.d文件不存在时会打印错误,有强迫症的同学肯定感觉不满意,这个问题我们后面解决。

参考源码

可以从GitHub下载源码。

GitHub

小结

利用GCC生成.d文件,再用include引入Makefile,可解决一个.c文件应该如何正确触发编译的问题。

查看官方手册:



Comments

Loading comments...