头文件和源文件在不同目录情况下 Makefile自动推导依赖关系的实现

时间:2022-02-05 12:38:51

参考 跟我一起写Makefile http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=408225&extra=&authorid=10610714&page=1


首先回顾《跟我一起写Makefile》里有一节 自动生成依赖性。利用gcc中的一个编译选项 -MM,得到源文件的依赖文件,然后将其写入一个.d文件,每次make命令执行时,利用include 命令将.d文件内的依赖关系包含进makefile里,这样每个源文件是否需要重新编译可根据.d依赖文件的情况判定。

    %.d: %.c
            set -e; rm -f $@; \
             $(CC) -M $(CPPFLAGS) $< >; $@.$$$$; \
             sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
             rm -f $@.$$$$

这段是《跟我一起写Makefile》里生成依赖文件.d的代码。

set -e ; rm -f $@; 第一句将shell设置为当命令输出非0时则退出,第二句删除原有.d文件

$(CC) -M $(CPPFLAGS) $< >; $@.$$$$;
该命令对源文件生成 依赖文件,并将其保存为$@.$$$$,后面的一串$$$$表示文件的后缀是一个随机码(听说是表示进程号),反正是为了不可能与其他文件重复,只是作为一个临时文件而已

sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;
该命令是利用shell里的sed命令中的 sed 's/原有内容/新内容/g' ,完成一个内容替换,其中“原有内容”和“新内容”可以用模式表达,'/'也可以用','代替。

而后面的 "< $@.$$$ > $@"则是个shell命令的一个流导向,作用是将上一个命令生成的$@.$$$$文件内容导入到sed命令来进行替换处理(该处理是以行为单位进行的),接着将替换后的结果保存到 $@文件中

最后一行则是将临时生成的文件删除。


怎样将这一段代码插入到makefile中实现自动推导依赖关系,使我们写makefile时可以不用事先知道每个源文件具体包含哪些头文件?

这个可以通过include命令来实现,下面通过一个具体实例来说明

代码文件结构

main.cpp 包含 hello.h

hello.cpp 包含 hello.h


这时我们可以这样写makefile(此时所有头文件和源文件都在同一个目录,且makefiel也在该目录)

ifndef GXX
	GXX := g++
endif

ifndef CXXFLAGS
	CXXFLAGS := -g
endif

CxxSources := $(shell find -iname *.cpp ) #自动寻找当前目录下的源文件

Objs := $(CxxSources:.cpp=.o)

execfile:$(Objs)
	$(GXX)	-o $@ $(CXXFLAGS)  $(Objs)

-include $(CxxSources:.cpp=.d)

%.d : %.cpp
	rm -f $@; \
	$(GXX) -MM $(INCLUDE) $< > $@.$$$$; \
	sed -e 's,^.*:,$*.o $@:,g'  < $@.$$$$ > $@; \
	rm -f $@.$$$$

.PHONY : clean

clean:
	@echo $(CxxSources)
	rm execfile $(CxxSources:.cpp=.d) $(CxxSources:.cpp=.o) 

通过include命令,在每次执行make时,都会先将*.d文件内各个目标文件.o的依赖关系导入,这样就像自己显示方式在makefile里添加目标的依赖关系一样。


上面这个方式是所有源文件和头文件在同一个目录下,那么当工程复杂时需要头文件可能和源文件在不同的目录下,并且makefile与头文件和源文件在不同目录下,上面的方法就不行了。在上面生成.d文件文件过程中,我们可以继续进一步添加内容(源文件的编译过程),使编译不是按隐式规则进行。

这时代码结构改变为

工程目录ex1,在该目录下有head和src目录,分别存放头文件和源文件,makefile文件在ex1下。头文件和源文件的依赖关系跟上面一样。这时makefile为

ifndef GXX
	GXX := g++
endif

ifndef CXXFLAGS
	CXXFLAGS := -g
endif

INCLUDE := -Ihead

CxxSources := $(shell find -iname *.cpp )

Objs := $(CxxSources:.cpp=.o)

execfile:$(Objs)
	@echo "CXXSources:" $(CxxSources)
	@echo "Objs:" $(Objs)
	$(GXX)	-o $@ $(CXXFLAGS)  $(Objs)

-include $(CxxSources:.cpp=.d)

%.d : %.cpp
	rm -f $@; \
	$(GXX) -MM $(INCLUDE) $< > $@.$$$$; \sed -e 's,^.*:,$*.o $@:,g' -e 's,$$,; $(GXX) -c $(CXXFLAGS) $(INCLUDE) $<,g'  < $@.$$$$ > $@; \
	rm -f $@.$$$$

.PHONY : clean

clean:
	@echo $(CxxSources)
	rm execfile $(CxxSources:.cpp=.d) $(CxxSources:.cpp=.o) 

通过这种方式,生成的每个.d文件内不但包含了.o目标的依赖关系,还包含了一条编译命令生成.o
$(GXX) -c $(CXXFLAGS) $(INCLUDE) $<,g'  < $
这条命令指定了编译时自动搜索head文件夹的内容,如果不加这个内容,采用隐式规则生成时会提示找不到.cpp源文件包含的头文件。


后来,我发现一个更简单的方法,即CXXFLAGS += INCLUDE ,这样不要在.d文件里添加自定义的编译规则了。