在源码中,我们通常都会包含一系列的头文件,当我们通过Makefile来组织这些源文件参加编译时,需要把相应的头文件也要加到Makefile的依赖当中。如在layerControl_wenston.c中包含#include <cdx_log.h>,那么我们就应该存在以下依赖关系:
layerControl_wenston.o:layerControl_wenston.c cdx_log.h
否则,当我们修改cdx_log.h,如果没有进行clean操作,则会有可能导致我们的修改没有及时的更新。
但是,当遇到一个比较大型的工程目录结构时,你就得必须小心翼翼的处理这些头文件的依赖,修改源码也得修改对应的Makefile,这会产生大量的维护工作,并且极容易造成错误。在编译器如此智能的情况下,我们可以使用c/c++编译器的自动推导功能"-M"选项,即自动寻找源文件中包含的头文件,并生成一个依赖文件,例如执行以下命令:
gcc -M layerControl_wenston.c
其输出是
layerControl_wenston.o:layerControl_wenston.c cdx_log.h
这样就避免了自己手动来决定众多文件的依赖关系了,而是自动由编译器推导生成。如果使用GNU的编译器,需要使用"-MM"参数,否则使用"-M"会把一些标准头文件也包含进来。
如何把编译器这个功能使用起来呢?GNU组织建议编译器为每个源文件自动生成的依赖关系放到一个文件中。为每个*.c文件都产生一个*.dep的Makefile文件,.dep文件中就存放对应.c文件的依赖关系。
下面给出一个规则来产生.dep文件:
%.dep: %.c
@echo "Creating $@ ..."
@set -e; \
rm -rf $@.tmp; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
sed 's,\(.*\)\.o[ :]*,objs/\l.o $@: ,g' < $@.tmp > $@; \
rm -rf $@.tmp
这个规则的意思如下:
"set -e"的作用是告诉shell,在生成依赖关系文件的过程中,如果出现任何错误就直接退出。shell异常退出的最终表现就是make会告诉我们出错了,从而停止后续的make工作。如果不进行这一设置,当构建依赖文件出错时,make还会继续后面的工作,并最终出错,这不是我们所希望的。
几个需要注意的点:
1.对于规则中的每条命令,make都是在一个新的shell上运行它的。
2.如果希望多条命令在同一个shell中运行,可以使用;将这些命令连在一起。
3.当命令很长时,可以使用"\"将一个命令分成多行书写。
4.$@,用于表示一个规则中的目标,当一个规则中有多个目标时,$@表示任何造成规则命令被执行的目标
5.$^,表示的是规则中的所有先决条件。
6.$<,表示的是规则中的第一个先决条件。
“rm -rf $@.tmp; \”,表示删除每个目标的临时文件*.dep.tmp。
“$(CC) -E -MM $(filter %.c, $^) > $@.tmp; \”,表示为每个.c文件创建一个临时依赖文件*.dep.tmp。
“sed ‘s,(.).o[ :],objs/\l.o $@: ,g’ < $@.tmp > $@; \”,表示通过sed命令把*.dep.tmp文件重定位到*.dep文件中。
最后,通过以下语句引入依赖文件:
SRCS = $(wildcard *.c)
-include $(SRCS:.c=.dep)
以下是我源文件的目录结构和对应Makefile的例子:
Makefile例子:
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRCS))
DEPS = $(SRCS:.c=.dep)
OUT_BIN = mediaplayer
all: $(OUT_BIN)
-include $(DEPS)
$(OUT_BIN): $(OBJS)
$(CC) -o $@ $(filter %.o, $^) $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $(filter %.c, $^)
%.dep: %.c
@echo "Creating $@ ..."
@set -e; \
rm -rf $@.tmp; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
sed 's,\(.*\)\.o[ :]*,objs/\l.o $@: ,g' < $@.tmp > $@; \
rm -rf $@.tmp
.PHONY: clean
clean:
rm -rf *.o
rm -rf $(OUT_BIN)
rm -rf *.dep