现在我们的主makefile已经是这样的
# main.mk
#
include build/systems/system.mk
## 定义产品信息。这里BUILD_SPECS设置为spec.mk
PRODUCT_SPECS :=build/spec.mk
include build/products/product.mk
## 定义平台相关的编译命令。这里PLATFORM_SPECS为空,表示我们不做额外定制化。
PLATFORM_SPECS :=
include build/platforms/platform.mk
## 将产品信息中的定义的feature与平台定义的编译选项混合
CXXFLAGS+=$(OPTIONS)
OBJS:=hello.o main.o
TARGET:=hello.exe
## 定义obj文件的编译规则
hello.o : hello.cpp hello.h
$(CXX) $(CXXFLAGS) -c $< -o $@
main.o : main.cpp hello.h
$(CXX) $(CXXFLAGS) -c $< -o $@
## 定义可执行文件hello.exe的编译规则
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^
all: ...
在此之际,回顾一下一开始提到的最原始的makefile的几个毛病
地方
1. makefile内容依赖于源文件。如果再增加一个world.cpp, 则必须再修改makefile后才能编译。只好期望源文件不要太多了
2. 生成obj文件时,依赖的源文件和头文件是手动添加的,不会自动推导。这就导致以后修改源文件时,必须同步维护makefile中头文件依赖关系。太累了
3. g++ 以及相应的编译选项都是写死的,如果想要交叉编译一个arm平台上运行的可执行文件,哦,又要修改makefile
4. 没有编译产品的信息,好歹给个立牌坊的机会
想现在毛病3,4已经基本漂亮的解决了(“基本”的含义是?,请看后文),地也圈了,楼也盖了不少(products目录,platforms目录,systems目录),本来坐等发财,结果发现就因为毛病2,集团内容沟通不利。随便改改源文件,可以就要同步调整makefile了。要解决这个头文件依赖关系的自动推导问题,呵呵,GNU中直接有尚方宝剑MMD,MF, MT。
SRC_FILES:= hello.cpp main.cpp
OBJS:=hello.o main.o
TARGET:=hello.exe
## -MMD 可以用于自动生成头文件依赖关系
%.o : %.cpp
$(CXX) $(CXXFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -MT"$@" -MT"$(@:%.o=%.d)" -c "$<" -o "$@"
DEPS := hello.d main.d
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEPS)
endif
## 定义可执行文件hello.exe的编译规则
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^
关于hello.d, main.d,他们的内容就是具体的依赖关系,然后通过include将依赖关系导入,如此就大功告成。
## hello.d
hello.o hello.d: hello.cpp hello.h
hello.h:
详细的,hello.d具体怎么生成的,或者其他更多自动生成头文件依赖关系的,就“不要问我从哪里来,我的答案在度娘”
现在依赖关系是自动生成了,不过obj文件列表,还有那些.d文件列表,都是手动填写的,可以自动化吗?
答案是肯定的,要不然通用makefile就炼不成了。(就像是看电视剧或者动漫的,都知道主角光环,无论前面如何复杂艰险,主角总是能化险为夷的)
makefile中有一种类似于shell脚本中的变量替换一样,我们可以直接从源文件列表导出obj文件列表,以及.d文件列表。不多说,直接上代码
SRC_FILES:= hello.cpp main.cpp
OBJS:=$(SRC_FILES:.cpp=.o)
DEPS:=$(OBJS:.o=.d)
TARGET:=hello.exe
## -MMD 可以用于自动生成头文件依赖关系
%.o : %.cpp
$(CXX) $(CXXFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -MT"$@" -MT"$(@:%.o=%.d)" -c "$<" -o "$@"
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEPS)
endif
## 定义可执行文件hello.exe的编译规则
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^
如果你愿意,源文件列表也是可以自动推导的:给定源文件路径,通过$(foreach dir, $(LOCAL_SRC_PATHS), $(wildcard $(dir)/*.cpp))命令,可以自动将指定目录下的.cpp文件全部找出来。
今天的工作做得很漂亮,以后只要配置好makefile,以及列出源文件列表,就可以直接编译了,其他什么也不用改。甚至对于源文件列表,也可以通过指定目录以及命令wildcard自动推导出来。
走到这里,感觉人生已经无欲无求了,通用makefile真的已经通用了。However,我又是看得太远,看这段makefile,我想到两个问题
1. 上面的生成obj文件和.exe文件的代码是很形式化的,如果可以应该同product一样,单独归类到某个文件夹中,防止在修改makefile时不小心改了这些代码
2. 如果我是多模块编译,怎么办呢?
路漫漫其修远兮,吾将上下而求索...