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变量中(+=赋值)。