Linux C : Makefile 的编写和示例

        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中写入如下代码

Linux C : Makefile 的编写和示例

在主程序 helloword.c 写入如下代码 ,可以看出 主程序中引用到了 mysum.c 

Linux C : Makefile 的编写和示例

 

如果要编译 以上两个文件,那么要在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

上一篇:Makefile笔记


下一篇:Makefile学习笔记