定义: makefile定义了软件开发过程中,项目工程编译链、接接的方法和规则。
产生: 由IDE自动生成或者开发者手动书写。
作用: Unix(MAC OS、Solaris)和Linux(Red Hat、Ubuntu、SUSE)系统下由make命令调用当前目录下的makefile文件执行,可实现项目工程的自动化编译。
target:prerequisites
command
其中,target为需要生成的目标,prerequisites为依赖项,command为make需要执行的命令(任意的Shell命令)。
注意:其中command前必须以tab键开始。
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
1.显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2.隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
3.变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4.文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,请参考文末的参考资料。
5.注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。
在默认的方式下,也就是我们只输入make命令,则相当于make first_objname_in_makefile
。意思是生成出现在makefile中第一个目标文件。或者指明生成的目标名称,如make objname
。
按默认方式,输入make命令,其工作方式是:
1.make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2.如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,并把这个文件作为最终的目标文件。
3.如果target不存在,则根据target后的依赖项和command生成target。如果target已存在,则make检测target依赖项是否有修改,若修改,则跟新target。
鄙人将以实际工作项目的makefile为例为大家讲解makefile的创建过程。Makefile内容如下:
CCFILES += $(wildcard src/*.cpp)
SRCDIR := ./src/
VPATH = src:./include:./src/xmlparser:./lib
#Compilers
#CC := /opt/intel/composer_xe_2013.0.079/bin/intel64/icpc
CC := icpc
#Compilers para
FLAGS := -openmp -openmp-report -vec-report-O2
OBJECT :=file_interface.o tinyxml2.omic_decomposer.o Charset.o network.o buffer.o \ task_queue.o common.o main.omic_function.o hashtree.o nodeconfig.o md5_mic.o ntlm_mic.o
ALG.out : $(OBJECT)
$(CC)$(FLAGS) -o ALG.out $(OBJECT) ./lib/libxmlextern.a
file_interface.o :global.h tinyxml2.hfile_interface.h $(SRCDIR)file_interface.cpp
$(CC)$(FLAGS) -c $(SRCDIR)file_interface.cpp
tinyxml2.o :tinyxml2.h$(SRCDIR)tinyxml2.cpp
$(CC)$(FLAGS) -c $(SRCDIR)tinyxml2.cpp
mic_decomposer.o :$(SRCDIR)mic_decomposer.cppmic_decomposer.h mic_define.h cnre.h \ common.h hashtree.h
$(CC)$(FLAGS) -c $(SRCDIR)mic_decomposer.cpp
Charset.o :Charset.h cnre.h$(SRCDIR)Charset.cpp
$(CC)$(FLAGS) -c $(SRCDIR)Charset.cpp
network.o :network.h task_queue.hnetwork_packet.h nodeconfig.h $(SRCDIR)network.cpp
$(CC)$(FLAGS) -c $(SRCDIR)network.cpp
buffer.o :network.h task_queue.hnetwork_packet.h nodeconfig.h buffer.h $(SRCDIR)buffer.cpp
$(CC)$(FLAGS) -c $(SRCDIR)buffer.cpp
task_queue.o :task_queue.h$(SRCDIR)task_queue.cpp
$(CC)$(FLAGS) -c $(SRCDIR)task_queue.cpp
common.o :common.h mic_define.hmic_function.h $(SRCDIR)common.cpp
$(CC)$(FLAGS) -c $(SRCDIR)common.cpp
main.o :task_queue.h global.h network.hnetwork_packet.h common.h nodeconfig.h \ mic_define.h mic_function.hfile_interface.h $(SRCDIR)main.cpp
$(CC)$(FLAGS) -c $(SRCDIR)main.cpp
mic_function.o :mic_function.h$(SRCDIR)mic_function.cpp
$(CC)$(FLAGS) -c $(SRCDIR)mic_function.cpp
hashtree.o :hashtree.h$(SRCDIR)hashtree.cpp
$(CC)$(FLAGS) -c $(SRCDIR)hashtree.cpp
nodeconfig.o :nodeconfig.h./src/xmlparser/tinyxml.h $(SRCDIR)nodeconfig.cpp
$(CC)$(FLAGS) -c $(SRCDIR)nodeconfig.cpp
md5_mic.o :simd.h mic_define.h common.hshare_macro.h md5_macro.h md5_mic.h \ file_interface.h $(SRCDIR)md5_mic.cpp
$(CC)$(FLAGS) -c $(SRCDIR)md5_mic.cpp
ntlm_mic.o :simd.h mic_define.h common.hshare_macro.h ntlm_macro.h ntlm_mic.h
$(CC)$(FLAGS) -c $(SRCDIR)sha256_mic.cpp
.PHONY clean:
rm-f *.o *.out
具体讲解:
#this is annotation
CCFILES += $(wildcardsrc/*.cpp)
利用wildcard函数获取src目录下所有.cpp文件,并赋值给自定义变量CCFILES。其中#号是makefile的注释符号,同shell。
SRCDIR:= ./src/
自定义变量SRCDIR用于指明.cpp源文件所在目录。SRCDIR变量在command中出现时,以类似于宏替换的方式将其载入command中。
VPATH= src:./include:./src/xmlparser:./lib
指明makefile寻找依赖项时,若当前目录不存在,则去VPATH指明的目录去寻找。各目录以“:”号隔开。
CC := icpc
自定义变量CC指明为编译器为icpc,表示使用Intelc++ Compiler作为项目的编译器。
FLAGS := -openmp -openmp-report-vec-report -O2
指明编译选项。
OBJECT :=file_interface.otinyxml2.o mic_decomposer.o Charset.o network.o buffer.o \
task_queue.o common.o main.o mic_function.o hashtree.onodeconfig.o md5_mic.o ntlm_mic.o
指明目标文件;其中反斜杠\表示一行还未结束。
ALG.out : $(OBJECT)
$(CC) $(FLAGS) -o ALG.out $(OBJECT) ./lib/libxmlextern.a
此处表示makefile需要生成的第一个目标文件,也就是不指明目标文件的make命令默认生成的目标文件。加入icpc的编译选项后,根据ALG.out依赖的目标文件和静态链接库项./lib/libxmlextern.a,进行连接生成可执行文件ALG.out。
file_interface.o :global.htinyxml2.h file_interface.h $(SRCDIR)file_interface.cpp
$(CC) $(FLAGS) -c$(SRCDIR)file_interface.cpp
指明file_interface.o的依赖项并编译成二进制文件file_interface.o。后面的每个目标文件皆是如此做法。
.PHONY clean
clean:
rm -f *.o *.out
使用.PHONY关键字,指明clean是伪目标,仅作标签使用。此处不依赖与任何项,使用方法是显示调用make clean,用于执行rm操作。但也可以添加依赖项,如:
all : prog1 prog2 prog3
.PHONY : all
则all依赖于prog1 prog2 prog3这三个文件,那么使用 makeall则可以生成三个目标文件prog1、prog2和prog3。若将all放在所有目标文件的前面,则使用make即可,无需指明make all,原因是make命令将makefile中第一个出现的目标作为最终目标,若不放在最前面,则必须指明make all。
= 是最基本的赋值,以makefile中最后赋值为准;
:= 是覆盖之前的值,以当前赋值为准;
?= 是如果没有被赋值过就赋予等号后面的值;
+= 是添加等号后面的值,即拼接等号后面的值。
其中=和:=的区别见如下代码:
(1)“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
x = foo
y = $(x) bar
x = xyz
在上例中,y的值将会是 xyz bar,而不是 foo bar 。
(2)“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo
y := $(x) bar
x := xyz
在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。
不一定,可以不包含进去。makefile是根据依赖项是否被修改决定是否重新执行command。如果不把头文件写入依赖项中,则面临的风险就是修改了头文件,目标文件不会被重新编译。我们的原则是,自己定义的头文件写入依赖项,库的头文件无需包含,除非你要修改库的头文件。
VPATH是makefile的特殊变量,只能用来指明makefile寻找目标文件的依赖项所在的目录,不能帮助编译器寻找所需编译的文件。
vpath是makefile的关键字,VPATH是makefile的特殊变量,两者的区别在于VPATH指定全局的搜索路径,而vpath可以针对特定的文件搜索路径。
vpath命令有三种形式:
vpath pattern path : 符合pattern的文件在path目录搜索。
vpath pattern : 清除pattern指定的文件搜索路径
vpath : 清除所有文件搜索路径。
例如:
vpath %.h ./include //指定.h类型文件的搜索路径是include
vpath %.cpp ./src //指定.cpp类型文件的搜索路径是src
make执行的命令前面加了@字符,则不显示命令本身而只显示它的结果。
替换变量中指定的内容有两种方式。
(1)模式匹配替换函数patsubst
用法如下:
res=$(patsubst %.c,%.o,$(var) )
以上表示将变量$(var)中所有以.c结尾的字符串变成.o结尾。
(2)使用变量的替换引用
这里用到makefile里的替换引用规则,即用指定的变量替换另一个变量。其的用法格式如下:
res=$(var:%.a=%.b)
例如:
foo:=a.c b.c
bar=$(foo:%.a=%.o)
那么bar就变成了a.o b.o。
以上表示将变量foo中以.a结尾的字符串替换成.b结尾并返回结果。注意,字符串处理函数并不会改变原有的字符串,变量的替换引用规则也不会改变原来字符串。实际上变量的替换引用是模式匹配替换函数patsubst的一个简化实现。
$@,$^,$<代表的意义分别是:
$@:目标文件;
$^:所有的依赖文件;
$<:第一个依赖文件。
通过以上特殊变量,可以简化Makefile。例如:
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
$(Target):$(OBJS)
$(MAKE) -C $(SUBDIR)
解释:当生成target目标对象时,会执行$(MAKE) -C $(SUBDIR)
这条命令,进入目录OBJDIR,该目录下有一个makefile,并执行。其中,$(MAKE) 值make预定义的变量,一般指的就是make,无需修改,可通过make -p查看make所有的预定义的变量。当然,也可直接指明为make,即make -C $(SUBDIR)
。
其中-C表示改变当前目录,make的命令选项可通过make -h查看。
如果想对子目录的进行make clean,该怎么做呢?
同理,进入相应的子目录之后再进行make clean,命令如下:
make clean -C $(SUBDIR) -f makefile
如果稍微复杂一点,还可以使用循环进入多个子目录进行make clean。这里需要在makefile中嵌入shell脚本,makefile参考代码如下:
SUBDIRS=subdir1 subdir2 subdir3
RECURSIVE_CLEAN=for subdir in $(SUBDIRS)\
do\
echo cleaning in $${subdir};\
(cd ${subdir} && $(MAKE) clean -f makefile)||exit 1;\
done
.PHONY: clean
clean:
$(RECURSIVE_CLEAN)
阅读以上代码,注意如下几点:
(1)shell脚本中,分号是多个语句之间的分隔符号,当一行只有一条语句的时候,末尾无需分号,当然加了也没错。
(2)当makefile内嵌shell脚本时,makefile中每一行的shell脚本需要一个shell进程来执行,不同行之间变量值不能传递。所以,makefile中的shell不管多长也要写在一行。因此,多行的shell需要在makefile使用连接符“\”连接为一行。此时,shell脚本中的一条语句后需要需要添加分号作为分隔。
(3)makefile中的变量需要通过$(variableName)
或者${variableName}
来引用。shell脚本中变量的引用方式是$variableName
和${variableName}
,不能通过$(variableName)
来引用。但是如果将shell脚本嵌入makefile中,引用shell变量,则需要$$
来引用,即$${variableName}
或者$$variableName
。
(4)makefile中在对一些简单变量的引用,我们也可以不使用“()”和“{}”来标记变量名,而直接使用$x
的格式来实现,此种用法仅限于变量名为单字符的情况。另外自动化变量也使用这种格式。对于一般多字符变量的引用必须使用括号了标记,否则make将把变量名的首字母作为作为变量而不是整个字符串($PATH
在makefile中实际上是$(P)ATH
)。
此两者均为通配符,但更准确的讲,%为Makefile规则通配符,一般用于规则描述,*为扩展通配符,用于扩展。如
%.o:%c
$(CC) $< -o $@
表示所有的目标文件及其所有依赖文件,然后编译所有目标文件的第一个依赖文件,并生成目标文件。再如:
$(filter %.c ,SOURCES)
此处SOURCES表示包含.c .cc .cpp等多类型源文件,该过滤器函数将c文件过滤出来,而%.c即为此过滤器规则。
通配符*则不具备上述功能。尤其是在Makefile中,当变量定义或者函数调用时,通配符%的展开功能就失效了。此时需要借助wildcard函数。通配符*常用于wildcard函数中,二者应用范围不同。
PHONY的用法:
.PHONY Target1 Target2
PHONY的作用:
指明Target是伪目标,并不会真正生成Target目标文件。Target是显示请求执行命令的名称。
必须使用PHONY的情况:
当存在与命令名称同名的文件时,一定要使用PHONY来描述命令名,因为命令名没有依赖文件,如果同名的文件始终是最新文件,那么显示make命令名时,该命令永远不会被执行。为避免这个问题,可使用”.PHONY”指明该目标。如:
.PHONY : clean
clean:
rm -f *.out *.o
这样执行make clean
会无视clean文件存在与否,是否是最新的。直接执行clean这个伪目标依赖的命令。
假设源文件均为.cpp文件,那么简洁的,通用的makefile模板可以书写为如下格式:
DIR_SRC0 = ./src0
DIR_SRC1 = ./src1
DIR_OBJ = ./obj
DIR_BIN = ./bin
#通过通配符扩展函数wildcard在多个原文件目录寻找源文件
SRC = $(wildcard ${DIR_SRC0}/*.cpp) $(wildcard ${DIR_SRC1}/*.cpp)
OBJ = $(patsubst %.cpp,${DIR_OBJ}/%.o,$(notdir ${SRC}))
TARGET = main.out
BIN_TARGET = ${DIR_BIN}/${TARGET}
CC = g++
CFLAGS = -g -Wall -I${DIR_INC} -DDEBUG
${BIN_TARGET}:${OBJ}
$(CC) $(OBJ) -o $@
#利用makefile自动推导功能和自动化变量,用一条语句实现同一个目录下多个源文件的编译
#以下为根据多个源文件的目录添加多个
${DIR_OBJ}/%.o:${DIR_SRC0}/%.cpp
$(CC) $(CFLAGS) -c $< -o $@
${DIR_OBJ}/%.o:${DIR_SRC1}/%.cpp
$(CC) $(CFLAGS) -c $< -o $@
#下面可以添加每个目标文件的依赖的头文件,来实现头文件的更新带动目标文件的更新
#当然也可以不添加,但是这样做带来的后果就是,当修改了某个头文件,include该头文件的源文件不会被重新编译。这一点要切记。
${DIR_OBJ}/main.o : defs.h
${DIR_OBJ}/kbd.o : defs.h command.h
${DIR_OBJ}/command.o : defs.h command.h
${DIR_OBJ}/display.o : defs.h buffer.h
${DIR_OBJ}/insert.o : defs.h buffer.h
.PHONY:clean
clean:
find ${DIR_OBJ} -name *.o -exec rm -rf {}
解释如下:
(1)Makefile中的自动化变量 $@, $^, $<,$? 的意思:
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
(2)wildcard、notdir、patsubst的意思:
wildcard : 扩展通配符
notdir : 去除路径
patsubst :替换通配符
[1] Makefile经典教程(掌握这些足够)[转].http://blog.csdn.net/ruglcc/article/details/7814546/
[2] makefile百度百科
[3]http://www.cnblogs.com/hnrainll/archive/2011/04/12/2013377.html
[4]shell语句中需要分号分隔吗.http://zhidao.baidu.com/link?url=RzR1wT_CbFT67YEPS-DYAgunOw9NCZl9_cZWVWp3k9sT4KGkRT7ihAuHT43lstEr422WEVFB7hDtLI4zW_Vxt_
[5]makefile中的shell语法.http://blog.csdn.net/qingfengtsing/article/details/21252461
[6]多个文件目录下Makefile的写法.http://www.cnblogs.com/Anker/p/3242207.html