makefile用法用例与注意事项

一、makefile简介

定义: makefile定义了软件开发过程中,项目工程编译链、接接的方法和规则。

产生: 由IDE自动生成或者开发者手动书写。

作用: Unix(MAC OS、Solaris)和Linux(Red Hat、Ubuntu、SUSE)系统下由make命令调用当前目录下的makefile文件执行,可实现项目工程的自动化编译。

二、语法规则

target:prerequisites
command

其中,target为需要生成的目标,prerequisites为依赖项,command为make需要执行的命令(任意的Shell命令)。
注意:其中command前必须以tab键开始。

三、makefile内容

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命令,则相当于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

具体讲解:

1.通配符函数wildcard获取所有源文件

#this is annotation
CCFILES += $(wildcardsrc/*.cpp)

利用wildcard函数获取src目录下所有.cpp文件,并赋值给自定义变量CCFILES。其中#号是makefile的注释符号,同shell。

2.源文件目录

SRCDIR:= ./src/

自定义变量SRCDIR用于指明.cpp源文件所在目录。SRCDIR变量在command中出现时,以类似于宏替换的方式将其载入command中。

3.预定义变量VPATH指明目标的依赖项所在目录

VPATH= src:./include:./src/xmlparser:./lib

指明makefile寻找依赖项时,若当前目录不存在,则去VPATH指明的目录去寻找。各目录以“:”号隔开。

4.编译器

CC := icpc

自定义变量CC指明为编译器为icpc,表示使用Intelc++ Compiler作为项目的编译器。

5.编译选项

FLAGS := -openmp -openmp-report-vec-report -O2

指明编译选项。

6.反斜扛 \ 的作用

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

指明目标文件;其中反斜杠\表示一行还未结束。

7.第一个目标文件

ALG.out : $(OBJECT)
    $(CC) $(FLAGS) -o ALG.out $(OBJECT) ./lib/libxmlextern.a

此处表示makefile需要生成的第一个目标文件,也就是不指明目标文件的make命令默认生成的目标文件。加入icpc的编译选项后,根据ALG.out依赖的目标文件和静态链接库项./lib/libxmlextern.a,进行连接生成可执行文件ALG.out。

8.目标文件的生成

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。后面的每个目标文件皆是如此做法。

9.伪目标的使用

.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。

六、注意事项与知识点

1.makefile赋值符号= := +=?=的区别

= 是最基本的赋值,以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 了。

2.makefile中目标文件一定要把依赖的头文件包含进去吗?

不一定,可以不包含进去。makefile是根据依赖项是否被修改决定是否重新执行command。如果不把头文件写入依赖项中,则面临的风险就是修改了头文件,目标文件不会被重新编译。我们的原则是,自己定义的头文件写入依赖项,库的头文件无需包含,除非你要修改库的头文件。

3.VPATH的单一作用

VPATH是makefile的特殊变量,只能用来指明makefile寻找目标文件的依赖项所在的目录,不能帮助编译器寻找所需编译的文件。

4.VPATH与vpath的区别

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

5.makefile中shell命令前加@字符

make执行的命令前面加了@字符,则不显示命令本身而只显示它的结果。

6.变量的替换函数

替换变量中指定的内容有两种方式。

(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的一个简化实现。

7.Makefile中三个自动化变量:$@,$^和$<

$@,$^,$<代表的意义分别是:
$@:目标文件;
$^:所有的依赖文件;
$<:第一个依赖文件。

通过以上特殊变量,可以简化Makefile。例如:

main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<

8.makefile中如何调用子目录的makefile

$(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)。

9.Makefile中通配符*与%的区别是什么?

此两者均为通配符,但更准确的讲,%为Makefile规则通配符,一般用于规则描述,*为扩展通配符,用于扩展。如

%.o:%c
$(CC) $< -o $@

表示所有的目标文件及其所有依赖文件,然后编译所有目标文件的第一个依赖文件,并生成目标文件。再如:

$(filter %.c ,SOURCES)

此处SOURCES表示包含.c .cc .cpp等多类型源文件,该过滤器函数将c文件过滤出来,而%.c即为此过滤器规则。

通配符*则不具备上述功能。尤其是在Makefile中,当变量定义或者函数调用时,通配符%的展开功能就失效了。此时需要借助wildcard函数。通配符*常用于wildcard函数中,二者应用范围不同。

10.makefile中PHONY关键字的作用

PHONY的用法:

.PHONY Target1 Target2

PHONY的作用:
指明Target是伪目标,并不会真正生成Target目标文件。Target是显示请求执行命令的名称。

必须使用PHONY的情况:
当存在与命令名称同名的文件时,一定要使用PHONY来描述命令名,因为命令名没有依赖文件,如果同名的文件始终是最新文件,那么显示make命令名时,该命令永远不会被执行。为避免这个问题,可使用”.PHONY”指明该目标。如:

.PHONY : clean
clean:
    rm -f *.out *.o

这样执行make clean会无视clean文件存在与否,是否是最新的。直接执行clean这个伪目标依赖的命令。

11.多源文件目录的makefile模板

假设源文件均为.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

你可能感兴趣的:(makefile用法用例与注意事项)