上篇Demo执行文件的编译,直接参考了网上通用的Makefile万能模版,也算是一种简单的应用实践了。这篇开始考虑随着Demo开发工作推进,源码文件增多,甚至目录结构调整的场景。在这个场景下,Makefile的扩展性会显得十分必要,因为理想的目标是要能够让开发人员对Makefile进行最少、最傻瓜式的修改(甚至做到不修改)就能继续保持Demo编译工作的顺畅。因此,我们的工作应该是实现一套简单易使用的编译框架,使得开发同事只需在这个框架上依样画葫芦,就能实现对增删文件or目录的编译。最后,还有一点很重要的就是,一定要保证不能每次都全量编译,即只对最新的修改及其相关依赖的文件进行编译。(全量编译对于大型项目不可接受!再做强调!)
源码文件及所在目录结构有细小调整安排,理由就不说了,我想但凡功能规划明确的项目,即便功能模块或源码文件都增多,以下这种源码目录结构的安排也算是典型通用的。该目录结构的变动,会直接导致原来的Makefile也需要重新调试,单一Makefile虽然编写简单,但不易维护,在这就体现了弊端。至于怎么由单一Makefile变成多Makefile的过程,我也直接跳过了,只能说这是实践后的结果,具体如下图:
针对变动后的Makefile内容,如下,有必要的在源码中注释。
#./demo_prj_v1.1/Makefile
# ---------------------------------------------------------------------------
#
# Make for demo
#
# ---------------------------------------------------------------------------
# CROSS_COMPILE ?= xxx-linux-gnu-
CROSS_COMPILE ?=
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
AR = $(CROSS_COMPILE)ar
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
export CC LD
# shell命令,这样可以做到全局可控
ECHO = @echo
MKDIR = mkdir -p
RM = @rm -rf
MAKE = make -j # -j支持并行编译
export ECHO MKDIR RM
# ---------------------------------------------------------------------------
# Compiler dir_env define
# ---------------------------------------------------------------------------
TOP_DIR := $(shell pwd)
BUILD_DIR := $(TOP_DIR)/build
BIN_DIR := $(BUILD_DIR)/bin
OBJS_DIR := $(BUILD_DIR)/objs
# Attempt to create a output target directory.
$(shell [ -d ${BUILD_DIR} ] || $(MKDIR) ${BUILD_DIR} && $(MKDIR) ${OBJS_DIR} && $(MKDIR) ${BIN_DIR})
export TOP_DIR OBJS_DIR
# ---------------------------------------------------------------------------
# OBJS include the necessary directories and the source files
# ---------------------------------------------------------------------------
SUB_DIRS = public lib demo
ALL_OBJS = $(addprefix $(OBJS_DIR)/, $(addsuffix .o, $(SUB_DIRS)))
INCLUDE = -I$(TOP_DIR)/public/include \
-I$(TOP_DIR)/lib/include \
-I$(TOP_DIR)/demo/include
export INCLUDE
CFLAGS = -g
.PHONY: all clean $(SUB_DIRS)
all: $(BIN_DIR)/demo_app
$(BIN_DIR)/demo_app: $(ALL_OBJS)
$(ECHO) Build demo_prj start...
$(CC) $(ALL_OBJS) -o $@
$(ECHO) Done!!!
$(ALL_OBJS): $(SUB_DIRS)
$(SUB_DIRS):
$(MAKE) -C $@
clean:
$(RM) $(shell find ./ -name "*.o")
$(RM) $(BIN_DIR)/demo_app
$(ECHO) clean demo_prj over...
#./demo_prj_v1.1/public/Makefile
MODULE = public
SUB_SRCS_DIR = $(TOP_DIR)/$(MODULE)/source
SUB_OBJS_DIR = $(OBJS_DIR)/$(MODULE)
INCLUDE +=
SUB_ALL_SRCS := $(notdir $(wildcard $(SUB_SRCS_DIR)/*.c))
SUB_TMP_OBJS := $(SUB_ALL_SRCS:.c=.o)
SUB_ALL_OBJS := $(addprefix $(SUB_OBJS_DIR)/, $(SUB_TMP_OBJS))
all: $(OBJS_DIR)/$(MODULE).o
$(OBJS_DIR)/$(MODULE).o: $(SUB_ALL_OBJS)
$(ECHO) Linking $(MODULE).o...
$(LD) -r -o $@ $(SUB_ALL_OBJS)
$(SUB_OBJS_DIR)/%.o: $(SUB_SRCS_DIR)/%.c
$(ECHO) Compiling $(notdir $<)...
$(shell [ -d $(SUB_OBJS_DIR) ] || mkdir -p $(SUB_OBJS_DIR))
$(CC) $(INCLUDE) $(CFLAGS) -c -o $@ $<
#./demo_prj_v1.1/lib/Makefile
MODULE = lib
SUB_SRCS_DIR = $(TOP_DIR)/$(MODULE)/source
SUB_OBJS_DIR = $(OBJS_DIR)/$(MODULE)
INCLUDE +=
SUB_ALL_SRCS := $(notdir $(wildcard $(SUB_SRCS_DIR)/*.c))
SUB_TMP_OBJS := $(SUB_ALL_SRCS:.c=.o)
SUB_ALL_OBJS := $(addprefix $(SUB_OBJS_DIR)/, $(SUB_TMP_OBJS))
all: $(OBJS_DIR)/$(MODULE).o
$(OBJS_DIR)/$(MODULE).o: $(SUB_ALL_OBJS)
$(ECHO) Linking $(MODULE).o...
$(LD) -r -o $@ $(SUB_ALL_OBJS)
$(SUB_OBJS_DIR)/%.o: $(SUB_SRCS_DIR)/%.c
$(ECHO) Compiling $(notdir $<)...
$(shell [ -d $(SUB_OBJS_DIR) ] || mkdir -p $(SUB_OBJS_DIR))
$(CC) $(INCLUDE) $(CFLAGS) -c -o $@ $<
#./demo_prj_v1.1/demo/Makefile
MODULE = demo
SUB_SRCS_DIR = $(TOP_DIR)/$(MODULE)/source
SUB_OBJS_DIR = $(OBJS_DIR)/$(MODULE)
INCLUDE +=
SUB_ALL_SRCS := $(notdir $(wildcard $(SUB_SRCS_DIR)/*.c))
SUB_TMP_OBJS := $(SUB_ALL_SRCS:.c=.o)
SUB_ALL_OBJS := $(addprefix $(SUB_OBJS_DIR)/, $(SUB_TMP_OBJS))
all: $(OBJS_DIR)/$(MODULE).o
$(OBJS_DIR)/$(MODULE).o: $(SUB_ALL_OBJS)
$(ECHO) Linking $(MODULE).o...
$(LD) -r -o $@ $(SUB_ALL_OBJS)
$(SUB_OBJS_DIR)/%.o: $(SUB_SRCS_DIR)/%.c
$(ECHO) Compiling $(notdir $<)...
$(shell [ -d $(SUB_OBJS_DIR) ] || mkdir -p $(SUB_OBJS_DIR))
$(CC) $(INCLUDE) $(CFLAGS) -c -o $@ $<
如果手动实践过上面Makefile内容,应该就已经能领悟出来脉络来了。这里总结提取框架的简单要点,都是说一千道一万的内容:
Makefile中的内容:
#./demo_prj_v1.2/Makefile
# ---------------------------------------------------------------------------
#
# Make for demo
#
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Compiler dir_env define
# ---------------------------------------------------------------------------
include democfg.mak
TOP_DIR := $(shell pwd)
BUILD_DIR := $(TOP_DIR)/build
BIN_DIR := $(BUILD_DIR)/bin
OBJS_DIR := $(BUILD_DIR)/objs
# Attempt to create a output target directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} && mkdir -p ${OBJS_DIR} && mkdir -p ${BIN_DIR})
export TOP_DIR OBJS_DIR
# ---------------------------------------------------------------------------
# OBJS include the necessary directories and the source files
# ---------------------------------------------------------------------------
SUB_DIRS = public lib demo
ALL_OBJS = $(addprefix $(OBJS_DIR)/, $(addsuffix .o, $(SUB_DIRS)))
INCLUDE = -I$(TOP_DIR)/public/include \
-I$(TOP_DIR)/lib/include \
-I$(TOP_DIR)/demo/include
export INCLUDE
.PHONY: all clean $(SUB_DIRS)
all: $(BIN_DIR)/demo_app
$(BIN_DIR)/demo_app: $(ALL_OBJS)
$(ECHO) Build demo_prj start...
$(CC) $(ALL_OBJS) -o $@
$(ECHO) Done!!!
$(ALL_OBJS): $(SUB_DIRS)
$(SUB_DIRS):
$(MAKE) -C $@
clean:
$(RM) $(shell find ./ -name "*.[o|d]")
$(RM) $(BIN_DIR)/demo_app
$(ECHO) clean demo_prj over...
#./demo_prj_v1.2/democfg.mak
#---------------------------------------------------------
# tool chain define
#---------------------------------------------------------
# CROSS_COMPILE ?= xxx-linux-gnu-
CROSS_COMPILE ?=
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
AR = $(CROSS_COMPILE)ar
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
#---------------------------------------------------------
# shell command
#---------------------------------------------------------
ECHO = @echo
MKDIR = mkdir -p
MV = @mv -r
RM = @rm -rf
MAKE = @make -j
#---------------------------------------------------------
# complier flags
# v for make debug
#---------------------------------------------------------
V ?=
ARFLAGS = rcs
CFLAGS = $(V) -g -MD
LDFLAGS = -static
#---------------------------------------------------------
# link libs
#---------------------------------------------------------
LIBS = -ln -lrt -lpthread -lutil
#./demo_prj_v1.2/demorules.mak
include $(TOP_DIR)/democfg.mak
SUB_ALL_SRCS := $(notdir $(wildcard $(SUB_SRCS_DIR)/*.c))
SUB_TMP_OBJS := $(SUB_ALL_SRCS:.c=.o)
SUB_ALL_OBJS := $(addprefix $(SUB_OBJS_DIR)/, $(SUB_TMP_OBJS))
all: $(OBJS_DIR)/$(MODULE).o
$(OBJS_DIR)/$(MODULE).o: $(SUB_ALL_OBJS)
$(ECHO) Linking $(MODULE).o...
$(LD) -r -o $@ $(SUB_ALL_OBJS)
$(SUB_OBJS_DIR)/%.o: $(SUB_SRCS_DIR)/%.c
$(ECHO) Compiling $(notdir $<)...
$(shell [ -d $(SUB_OBJS_DIR) ] || mkdir -p $(SUB_OBJS_DIR))
$(CC) $(INCLUDE) $(CFLAGS) -c -o $@ $<
#./demo_prj_v1.2/public/Makefile
include $(TOP_DIR)/demorules.mak
MODULE = public
SUB_SRCS_DIR = $(TOP_DIR)/$(MODULE)/source
SUB_OBJS_DIR = $(OBJS_DIR)/$(MODULE)
INCLUDE +=
include $(TOP_DIR)/demorules.mak
----------------------------------------------------
#./demo_prj_v1.2/lib/Makefile
MODULE = lib
SUB_SRCS_DIR = $(TOP_DIR)/$(MODULE)/source
SUB_OBJS_DIR = $(OBJS_DIR)/$(MODULE)
INCLUDE +=
include $(TOP_DIR)/demorules.mak
----------------------------------------------------
#./demo_prj_v1.2/demo/Makefile
MODULE = demo
SUB_SRCS_DIR = $(TOP_DIR)/$(MODULE)/source
SUB_OBJS_DIR = $(OBJS_DIR)/$(MODULE)
INCLUDE +=
include $(TOP_DIR)/demorules.mak
# 如果这些功能目录还需要有许多的子功能,从而又可以在该目录下增添各种功能子目录,
# 那么,我们在增添的子目录中,只需再按照这个模版填写子Makefile,通用规则中再增加
# 包含对子目录的编译。原理相同,不再举例。
编译后的结果:
相信看到这的朋友有注意到生成文件中多了.d文件,这是因为在democfg.mak文件中的CFLAG参数中增加了-MD,该选项的详细解释请参考《关于编译的一些事儿》(后续凡是编译碰到的诸如选项参数等奇葩问题,我一并记录在这篇博客中,权当备忘)。
继续查看这些.d文件,例如demo.d,如下:(...代表我本人的工作路径)
.../demo_prj_v1.2/build/objs/demo/demo.o: \
.../demo_prj_v1.2/demo/source/demo.c \
/usr/include/stdc-predef.h \
.../demo_prj_v1.2/demo/include/demo.h \
.../demo_prj_v1.2/public/include/public.h \
/usr/include/stdio.h /usr/include/features.h \
/usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h \
.../demo_prj_v1.2/lib/include/lib.h
include $(TOP_DIR)/democfg.mak
SUB_ALL_SRCS := $(notdir $(wildcard $(SUB_SRCS_DIR)/*.c))
SUB_TMP_OBJS := $(SUB_ALL_SRCS:.c=.o)
SUB_ALL_OBJS := $(addprefix $(SUB_OBJS_DIR)/, $(SUB_TMP_OBJS))
SUB_ALL_DEPS := $(SUB_ALL_OBJS:.o=.d)
# 将生成obj目录前移,因为生成.d也依赖该目录
$(shell [ -d $(SUB_OBJS_DIR) ] || mkdir -p $(SUB_OBJS_DIR))
all: $(OBJS_DIR)/$(MODULE).o
$(OBJS_DIR)/$(MODULE).o: $(SUB_ALL_OBJS)
$(ECHO) Linking $(MODULE).o...
$(LD) -r -o $@ $(SUB_ALL_OBJS)
#这里新增依赖规则,保证即使是首次编译,也必须先生成.d,再生成.o
$(SUB_OBJS_DIR)/%.o: $(SUB_SRCS_DIR)/%.c $(SUB_ALL_DEPS)
$(ECHO) Compiling $(notdir $<)...
$(CC) $(INCLUDE) $(CFLAGS) -c -o $@ $<
$(SUB_OBJS_DIR)/%.d: $(SUB_SRCS_DIR)/%.c
@set -e; rm -f $@; \
$(CC) $(INCLUDE) -MM $(CFLGAS) $< > $@.$ $ $ $(CSDN的BUG,4个‘$’中间无空格,下同); \
sed 's,\ ($*\ ).o[ :]*,$(SUB_OBJS_DIR)/\1.o $@ :,g' < $@.$ $ $ $ > $@; \
rm -f $@.$ $ $ $
# set -e:表示如果任何一句shell语句的执行结果不是true,则立即退出;
# -MM :不使用-M,表示不将标准库头文件包含进依赖关系里
# $ $ $ $ :表示为一个随机编号
# $* :表示目标模式中"%"及其之前的部分。这里,"$*"的值就是".o"之前的字符串
# sed 's,\ ($*\ ).o[ :]*,$(SUB_OBJS_DIR)/\1.o $@ :,g':这里用到sed的替换功能,格式 sed 's/old/new/g'。
# 其中,分隔符‘/’不是固定的,可以是任意字符,即sed 's,old,new,g'、sed 's|old|new|g’功能是一样的。
# < $@.$ $ $ $:表示$@.$ $ $ $文件必须已经存在才执行该语句
# > $@:将替换后的内容输出到目标文件中
# 使用-include,这样当所要包含的文件不存在时也不会有错误提示,make也不会退出;除此之外,和include功能一样。
-include $(SUB_ALL_DEPS)
.../demo_prj_v1.2/build/objs/demo/demo.o .../demo_prj_v1.2/build/objs/demo/demo.d : .../demo_prj_v1.2/demo/source/demo.c \
.../demo_prj_v1.2/demo/include/demo.h \
.../demo_prj_v1.2/public/include/public.h \
.../demo_prj_v1.2/lib/include/lib.h