Three Tricks to use Makefile Efficiently
最近读Protocol Buffer for C,发现一些Makefile的技巧
1. -MMD flag
Makefile比较麻烦的是写dependency。常用技巧是通过g++ -MM和sed脚本从.cpp源文件生成.d文件,这种方式可以解决自动生成依赖关系的问题,但其实有更好的方法:
g++ -MMD -c XXX.c
之后在Makefile里添加:
-include XXX.d
g++的文档提到-MMD:
-MMD Like -MD except mention only user header files, not system header files.
除了-MMD外,有意思的g++参数,可以参见gcc flags 。另外这篇详细讲解Makefile dependency的文章Makefile Autodependency。
2. Order-only prerequisites
order-only-prerequisites
用Makefile里的例子来说:
OBJDIR := objdir OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o) $(OBJDIR)/%.o : %.c $(COMPILE.c) $(OUTPUT_OPTION) $< all: $(OBJS) $(OBJS): | $(OBJDIR) $(OBJDIR): mkdir $(OBJDIR)
$(OBJS)需要$(OBJDIR),所以需要把$(OBJS)放在$(OBJDIR)前面;但在建立$(OBJDIR)之后,$(OBJ)就不在需要根据$(OBJDIR)的时间调整了。
3. Multiple line variables (Eval function)
Makefile里的变量可以有多行,这种变量要用define来定义。
使用这种变量有两种场合:(1)Canned Recipes; (2)Eval-Function
第一种就是把常用的一组命令包装在一起,比如:
define run-yacc = yacc $(firstword $^) mv y.tab.c $@ endef
之后调用:
foo.c : foo.y $(run-yacc)
第二种更加常用,比如下面的例子:
PROGRAMS = server client server_OBJS = server.o server_priv.o server_access.o server_LIBS = priv protocol client_OBJS = client.o client_api.o client_mem.o client_LIBS = protocol # Everything after this is generic .PHONY: all all: $(PROGRAMS) define PROGRAM_template = $(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%) ALL_OBJS += $$($(1)_OBJS) endef $(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog)))) $(PROGRAMS): $(LINK.o) $^ $(LDLIBS) -o $@ clean: rm -f $(ALL_OBJS) $(PROGRAMS)
首先上面注释行以下都是通用(generic)的。
其次eval会两次展开(expand)变量名,所以$(1)会被扩展成prog(比如server),$$($(1)_OBJS)会被扩展成$(server_OBJS),之后会再次被扩展成server.o server_priv.o server_access.o
最精彩的地方是如何把上面3个技巧联合起来。下面看一下cloudwu的Protocol Buffer for C项目的Makefile片段:
BUILD = build LIBSRCS = context.c varint.c array.c pattern.c register.c proto.c map.c alloc.c rmessage.c wmessage.c bootstrap.c stringpool.c LIBNAME = libpbc.$(SO) TESTSRCS = addressbook.c pattern.c PROTOSRCS = addressbook.proto descriptor.proto BUILD_O = $(BUILD)/o all : lib test lib : $(LIBNAME) clean : rm -rf $(BUILD) $(BUILD) : $(BUILD_O) $(BUILD_O) : mkdir -p $@ LIB_O := define BUILD_temp TAR := $(BUILD_O)/$(notdir $(basename $(1))) LIB_O := $(LIB_O) $$(TAR).o $$(TAR).o : | $(BUILD_O) -include $$(TAR).d $$(TAR).o : src/$(1) $(CC) $(CFLAGS) -c -Isrc -I. -fPIC -o $$@ -MMD $$< endef $(foreach s,$(LIBSRCS),$(eval $(call BUILD_temp,$(s)))) $(LIBNAME) : $(LIB_O) cd $(BUILD) && $(CC) --shared -o $(LIBNAME) $(addprefix ../,$^)
有意思的地方是BUILD_temp,将每一个$(1)的值赋给TAR,之后$(TAR)在$(BUILD_O)目录下编译,编译时的依赖关系被-include到Makefile内部(include前的减号,-,表示要include的文件即使不存在也不报错),同时把编译好的$(TAR).o加入到LIB_O变量中(+=赋值)。