make工具是Unix/Linux 的一个编译工具,它按照顺序读取 Makefile 或 makefile ,进行自动地有选择地执行编译链接,只对影响到的修改的文件进行重新编译,不需要对整个工程进行重新编译。而Makefile中些内容的就是它的编译方式。
Makefile 的格式:
目标项 | 依赖项列表 |
[target] : | file1 file2 file3 ... |
规则 | |
<tab> | command1 |
<tab> | command2 |
... | ... |
target是一个目标文件,也可以是Object File,也可以是执行文件。还可以是一个伪目标。这是一个文件的依赖关系,target这一个或多个的目标文件依赖于 依赖项列表中的文件,其生成规则定义在command中。说白一点就是说,依赖项列表中如果有一个以上的文件比target文件要新或者target文件不存在的话,command所定义的命令就会被执行,如果依赖不存在或者更旧,那么就会执行生成依赖的对应target。如果依赖列表中的伪目标总是会被执行,相对于子程序段一样,先从左开始判断依赖列表,如果依赖时文件则判断是否要更新,如果是伪目标则直接执行。伪目标所依赖的伪目标也会被跟着执行。为了避免伪目标和文件名冲突,可以显示指定伪目标,关键字为.PHONY:
依赖项就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令,通常是shell命令
例如
在一个文件 mysum.c中写入如下代码
在主程序 helloword.c 写入如下代码 ,可以看出 主程序中引用到了 mysum.c
如果要编译 以上两个文件,那么要在Makefile中写上
output: helloworld.c mysum.c #格式 目标头: 依赖文件列表
gcc -o output helloworld.c mysum.c #格式 每行开始必须是tab制表符
执行 make 或者 make -f Makefile 进行编译 。 其中 -f 指定一个编译过程的文件名(默认是Makefile,编译命令格式 make [目标名,默认值为第一个伪目标] -f [makefile文件名]。当连续执行第二次时,make就不会再构建没被更改的目标。
Makefile的编写也可以作些改进,例如把一些关键命令定义成变量,写在文件头,例如gcc ,假如之后要改用g++ 来编译呢,难不成要在全文一个个找出gcc再替换成g++?其实把关键命令定义成变量就可以应对编译方式的变更操作了。如果编译的文件太多达到几百个怎么办?可以用include 关键字来引用其他文件内容,再其他文件中用变量来接所有要编译的文件名。由于命令执行失败会自动中断make,可以再命令前加 ‘-’ 符号,表示此命令如果执行失败则无视报错继续执行往下的命令。
如果在makefile中匹配/查询文件,如果在当前目录未找到,可在(大写)VPATH和其子目录下继续找。 关于(小写)vpath语法, vpath [过滤条件] [查询目录] , 例如 vpath %.c /home/hu 表示 在makefile中匹配/查询文件,如果在当前目录未找到则可以在 /home/hu/目录和子目录下的所有 .c的后缀的文件名列表继续找。
Makefile 的自动变量
$@ | 当前目标名 |
$< | 第一个依赖项名 |
$^ | 所有依赖项名 |
$* | 不包含扩展名的当前依赖项名 |
$? | 比当前目标新的依赖项列表 |
$# | 依赖项个数 |
C++项目型Makefile示例
#变量定义
LIB_NAME = libQuantLib.so
-include ql_var.mk #这里面包含QL_SRC_DIR ,HEADER_DIR 等变量信息
CXX = g++
DEFIND = -DQL_ERROR_FUNCTIONS
DEFIND += -DQL_ERROR_LINES
CXXFLAGS = $(DEFINE) $(addprefix -I, $(HEADER_DIR)) -O2 -Wall -c -fmessage-length=0 -fpic -fno-strict-aliasing
HEADER_DIR = "."
HEADER_DIR += "$(BOOST_INCLUDE)"
HEADER_DIR += "$(ZLOG_INCLUDE)"
HEADER_DIR += "$(JAVA_HOME)/include"
HEADER_DIR += "$(JAVA_HOME)/include/linux"
SRC_DIR = $(QL_SRC_DIR) #源文件集合变量 ,定义在ql_var.mk
DEP_DIR = $(QL_DEP) #依赖集合变量 ,定义在ql_var.mk
vpath %.hpp $(HEADER_DIR)
vpath %.cpp $(SRC_DIR)
all: build_dir ql #执行make或者make all 相当于执行 make build_dir 和make ql
$(BUILD_DIR)/%.o : %.cpp #生成在build目录下的 .o文件的生成规则
$(CXX) $(CXXFLAGS) -MMD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o "$@" "$<"
-include $(DEP_FILE)
.PHONY: build_dir
build_dir:
@mkdir -p $(BUILD_DIR)
.PHONY: scan #执行 make scan 相当于先执行 make build_dir 再执行自身命令
scan: build_dir
@echo Scanning Project ....
@./scan.sh #执行shell,此shell的目的是创建ql_var.mk,并在此文件中定义要编译的文件和其依赖
ql: $(LIB_OBJ) $(QL_OBJ) # $(LIB_OBJ) $(QL_OBJ)为.o文件名集合,在ql_var.mk中被定义,如果发现依赖被更新则自动触发 .o文件的生成规则
$(CXX) -share -o $(LIB_NAME) $^ "$(ZLOG_LIB)/libzlog.a"
.PHONY: clean
clean:
@echo Clean Project
-@rm -rf $(BUILD_DIR)
-@rm -f ql_var.mk
执行编译顺序
make clean #清空编译目录和删除变量文件
make scan #创建编译目录和执行scan.sh ,从而创建变量文件
make #执行make all,创建编译目录 和链接目标文件(.o),需要链接的文件列表来源于变量文件中,如果依赖项缺失,则编译出依赖文件
Makefile全解可参考: https://blog.csdn.net/weixin_38391755/article/details/80380786