本Makefile适用于小规模软件工程。修改SOURCE_PATH变量设置源码路径,修改BINARY_PATH变量设置输出文件的路径,OBJECT_PATH变量为中间.o文件的存放目录,INCLUDE_PATH变量为头文件包含路径,TARGET变量为输出文件名。Makefile会输出依赖关系文件“xxx.o.d”,以便根据依赖关系只进行必要的编译工作。
# GNU的make工作时的执行步骤如下:
# 1.读入所有的Makefile。
# 2.读入被include的其它Makefile。
# 3.初始化文件中的变量。
# 4.推导隐晦规则,并分析所有规则。
# 5.为所有的目标文件创建依赖关系链。
# 6.根据依赖关系,决定哪些目标要重新生成。
# 7.执行生成命令。
# Makefile最基础语法格式(依赖关系):
# targets : prerequisites
# <tab>commands
# 上面的示例中目标名为targets,其依赖于prerequisites表示的文件,
# 其可能是源码文件、头文件、库文件、其它依赖目标等,如果依赖文件中有
# 任何一个文件的更新时间新于targets表示的文件,那么依赖生效,则会
# 执行依赖下面的commands指令。
# Makefile中的变量其实相当于C语言里面的宏,会在使用变量的地方做替换
# Makefile通配符:* , ? 和 ~
# ~ 表示当前用户的HOME目录
# * 表示全能匹配
# ? 表示
# Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是:
# $@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。
CC := g++
LD := g++
# 这是编译阶段使用的参数选项,一般是一些选项设置和头文件路径
CFLAGS += -std=c++11
# 这是链接阶段使用的参数选项,一般是链接库和链接库路径
LDFLAGS += -L./lib \
-lpthread \
-ldl
SOURCE_PATH ?= src src/a src/b src/c
BINARY_PATH ?=
OBJECT_PATH ?= objs
INCLUDE_PATH := -I./include $(foreach tmp,$(SOURCE_PATH),-I$(tmp))
TARGET ?= target
# wildcard 通配符,将所有的源文件的文件名都赋值给SOURCES变量
# SOURCES := $(wildcard *.c *.cpp src/*.cpp src/*/*.cpp src/*/*/*.cpp)
# SOURCES :=
SOURCES := $(foreach tmp,$(SOURCE_PATH),$(wildcard $(tmp)/*.c $(tmp)/*.cpp))
# notdir 过滤掉所有文件的路径信息,例如src/main.c变为main.c
# basename 去除文件的文件类型后缀,例如main.c变为main
# addprefix 添加一个前缀,为objects文件的存放目录,例如main变为objs/main
# addsuffix 添加一个后缀,为存放的中间文件的文件名,例如objs/main变为objs/main.o
OBJECTS := $(addsuffix .o,$(addprefix $(OBJECT_PATH)/,$(basename $(notdir $(SOURCES)))))
# OBJECTS := main.o display.o gpio.o video.o spi.o
# addsuffix 添加一个后缀,为存放的依赖文件的文件名
DEPENDS := $(addsuffix .d,$(OBJECTS))
# 第一个依赖,也是默认依赖,Makefile会根据这个依赖关系展开所有依赖
# 如果依赖的内容是其它的依赖目标,就会依次展开所有依赖目标的依赖关系,
# 例如这里就会展开$(TARGET)、test1、test2这三个依赖目标的依赖关系。
all : $(TARGET) test1 test2
# all : test1 test2
$(TARGET) : $(OBJECTS)
$(info LD $@)
$(LD) -o $@ $(OBJECTS) $(LDFLAGS)
# 可以将依赖关系和执行的命令分开描述,例如下面的两个依赖关系中没有直接
# 说明test1和test2两个依赖的命令部分,而是在后面定义了命令部分。
test1:
test2:
test1:
$(info SOURCES:$(SOURCES))
$(info OBJECTS:$(OBJECTS))
$(info DEPENDS:$(DEPENDS))
# $(foreach tmp,$(SOURCES),\
# $(info $(addprefix $(OBJECT_PATH)/,$(addsuffix .o,$(basename $(notdir $(tmp)))))))
test2:
$(info $@)
# 包含所有源文件的依赖关系,举其中一个依赖文件为例:
# objs/audio_test.o: src/audio_test.cpp include/TCloudRealtimeASR.h \
# include/TCloudRealtimeASRDefines.h include/TCloudRequestBuilder.h \
# include/TCloudHTTPSender.h include/TSpeex.h include/speex/speex.h \
# include/speex/speex_types.h include/speex/speex_config_types.h \
# include/speex/speex_bits.h
# 这是一个典型的依赖关系,‘.o’文件作为一个目标,其依赖于后面的所有源码文件和头文件,
# 如果后面的任何源文件发生修改都会导致依赖关系生效。但是没有描述依赖关系的命令,
# 这和上面的 test 依赖结构是一样的,在后面定义了依赖关系的命令。
-include $(DEPENDS)
# # eval函数用于动态添加makefile脚本,类似js中的eval。
# $(foreach tmp,$(OBJECTS),\
# $(eval $(tmp):;${CC} ${CFLAGS} -MMD -MF $(tmp).d -c -o $(tmp) $(addprefix $(SOURCE_PATH)/,$(addsuffix .cpp,$(basename $(notdir $(tmp)))))))
$(foreach tmp,$(SOURCES),\
$(eval $(addprefix $(OBJECT_PATH)/,$(addsuffix .o,$(basename $(notdir $(tmp))))):; \
${CC} ${CFLAGS} $(INCLUDE_PATH) \
-MMD -MF $(OBJECT_PATH)/$(basename $(notdir $(tmp))).o.d \
-c -o $(OBJECT_PATH)/$(basename $(notdir $(tmp))).o $(tmp)))
.PHONY : clean setenv
clean:
rm objs/*.o objs/*.d ${TARGET}
测试用例: