makefile学习总结

原贴:http://read.newbooks.com.cn/info/195391.html

这几天好好研究了下makefile,然后做了一个自认为还算智能的makefile。用的时候,只要把它放在项目里面,只要配置一下需要的几个参数,然后就可以make了。以后无论怎么添加代码文件,都不用去动这个makefile了,嘿嘿。

我先是研究了eclipse自动生成的makefile,然后在它的启发下,进行了改造,最终实现了自己的makefile。

下面是我写的这个makefile,我会对它做一个详细的说明。不过,这篇文章毕竟不是makefile的编写教程,因此也不可能写得太详细了。

下面开始:
首先进行的内容是参数设置部分,如下:

设置项目名字,它决定了我们make之后,生成的文件名。比如libXX.so或者XXX.a或者XXXX
#set your project name
PRJ_NAME=libXXX.so

设置项目的类型,是共享库、可执行程序还是静态库
#set your project type : choose one below
PRJ_TYPE =g++ -shared
#PRJ_TYPE = g++
#PRJ_TYPE = ar -r

设置编译的类型,是Debug还是Release
#set Debug or Release
Compile_Flag = Debug
#Compile_Flag = Release

设置编译后的文件的输出路径,这个文件夹一定要有才可以,否则会出错的。所以要事先建立好
#set your output path
Output:= bin

这里是设置代码所在的文件夹
#set your source folder
SRC:=code

如果引用了什么库,就在这里添加好了.
#add the lib you used here
#LIBS := -lLib1 -lLib2 -lLib3
LIBS :=
#LIBPATH := -Lpath1 -Lpath2 -Lpath3
LIBPATH :=

要设置的参数就这么多。下面进入第二部分,makefile核心内容的解释。
下面我仔细讲一下。
#符号,表示注释。makefile里面有它的那行,就不会起作用了。比如下面两行就是注释。
###################################
#DON"T MODIFY THE BELOWS

#combine output folder
FinalOutput := $(Output)/$(Compile_Flag)/
上面的代码,定义了一个变量,名字是FinalOutput,给它赋值,可以用=或者:=,等一下说区别。
$(Output)表示取变量Output的值,在这里,Output是bin,所以$(Output)就是bin啦。同理,#(Compile_Flag)就是Debug,组合在一起,就是bin/Debug/,把它赋值给变量FinalOutput,现在FinalOutput就是bin/Debug/了。

接下来说=和:=的区别。
=就是把右边的值赋给左边。但是,比如下面的赋值就会出问题
FinalOutput=$(FinalOutput)
为什么呢?
因为右边的$(FinalOutput)会取FinalOutput的值,这个取值的过程叫做“展开”,有点类似宏的意思。于是,这个展开就会陷入无穷的递归里面去了。虽然make很智能,遇到这类问题,它会抱错,但是我们怎么避免呢?于是就有了:=,它的意思就是只展开一次。这样就不会陷入无穷的递归里面去了。

#list all dirs
SUBDIRS := $(shell find $(SRC) -type d)
这行的作用,是调用shell,执行find命令,然后把返回的结果放到变量SUBDIRS里面。在makefile里面调用shell执行命令的方法是:
$(shell text)
其中,text是要执行的命令,比如上面的find $(SRC) -type d(按照前面的设置,这个命令展开后,应该是find code -type d。这个是基本的find命令,意思是查找code文件夹里面的所有文件夹,包括code文件夹。),命令执行的结果,也就是$(shell text)的返回值。

#flags in makefile
DEBUG_FLAG = -O0 -g3 -Wall -c -fmessage-length=0
RELEASE_FLAG = -O3 -Wall -c -fmessage-length=0
这里是设置编译器的编译参数,具体内容请参看g++的手册吧。如果有不满意的,可以在这里修改编译的参数。

RM := rm -rf
这个是清除命令,用在clean里面的

#set compile flag
ifeq ($(Compile_Flag),Debug)
CFLAGS := $(DEBUG_FLAG)
else
CFLAGS := $(RELEASE_FLAG)
endif
这里是一个条件判断。ifeq就是“如果等于”的意思。还有一个ifneq,当然就是“如果不等于”了。用法很简单,就是
ifeq(要比较的内容,要比较的内容)
else
endif

上面的代码,比较了Compile_Flag变量和Debug,如果Compile_Flag的值是Debug,就给变量CFLAGS设置为DEBUG_FLAG的值;如果不是,就设置为RELEASE_FLAG的值。

#divpare files
CPP_SRCS:=$(shell find ./$(SRC) -name *.cpp)
这里又调用了一次shell,执行了find。这次是查找.cpp文件,然后把文件列表保存在CPP_SRCS里面。找到的文件是带着相对的路径名的,这个很有用。

OBJS:=$(CPP_SRCS:.cpp=.o)
这里是替换,$(SRC_File:.cpp=.o)得意思就是说,把CPP_SRCS里面,每个字符串的.cpp部分,都替换成.o。说白了就是改了下扩展名。

OBJS:=$(OBJS:./=$(FinalOutput))
这里还是替换,是把./部分,替换成$(FinalOutput)。在这个例子里面,就是bin/Debug/。

举个例子吧。比如你在路径./code/test下面有个文件a.cpp,执行上面的操作后,CPP_SRCS里面就有了一个./code/test/a.cpp,然后经过替换,OBJS里面就有了一个bin/Debug/code/test/a.o了。

#all target
all:dir $(PRJ_NAME)
all是我们要make的目标,冒号(:)后面的内容是这个目标的依赖项,依赖项可以没有,也可以有多个。
这个all,就依赖于两个项目,一个是dir,一个是$(PRJ_NAME)。其中dir是用来创建目录的,而$(PRJ_NAME)是用来生成项目文件的。
make执行的时候,遇到all,要生成它,就得先找到它依赖的这两个项目。

dir:
 -mkdir $(FinalOutput)
 $(foreach val,$(SUBDIRS),mkdir $(FinalOutput)$(val);)
这个dir,没有依赖的项目,它要做的事情,就是上面那两行代码。每个目标要执行的操作,都要以tab开头,回车结束。如果一行太长,可以用\加回车在下一行继续。
在这里,这两行的作用,都是建立文件夹。
mkdir是shell里面建立文件夹的命令,而前面加个-,表示如果执行失败,就忽略错误继续。

而$(foreach val,$(SUBDIRS),mkdir $(FinalOutput)$(val);)
这一行,是个循环。它的意思是,从$(SUBDIRS)里面依次取值,放在变量val里面,然后执行后面的命令,也就是:mkdir $(FinalOutput)$(val);
因为这个命令会放到shell里面去执行,所以我没有在mkdir前面加-,因为shell不认的。
 
#tool invocations
$(PRJ_NAME):$(OBJS)
 @echo 'Building target: $@'
 @echo 'Invoking:GCC C++ Linker'
 $(PRJ_TYPE) $(LIBPATH) -o"$(FinalOutput)$(PRJ_NAME)" $(OBJS) $(LIBS)
 @echo 'Finished building target: $@'
 @echo ' '

在上面的命令里面,@echo 'Building target: $@'
表示显示一行字符串,显示内容就是''里面的。在echo前面加个@,意思就是告诉make,在执行的时候,不显示这行命令。如果没有前面的@,那么执行的结果会像下面这样:
echo 'test....'
test....

如果前面有@,结果会像下面这样:
test....
被执行的命令就不会显示出来了。

@echo 'Building target: $@'这行里面的$@是个特殊的系统变量,它代表“目标”,在这里就是$(PRJ_NAME)。假如$(PRJ_NAME)的值是testlib.so,那么$@就会是testlib.so了。

@echo 'Invoking:GCC C++ Linker'这行就是简单的显示些信息,没什么可说的。

$(PRJ_TYPE) $(LIBPATH) -o"$(FinalOutput)$(PRJ_NAME)" $(OBJS) $(LIBS)
这行才是真正干活的部分。前面准备了那么多,又说了那么多废话,其实真正干活的才刚刚遇到。这里的变量展开后,大概会如下形式:
g++ -shared -o"bin/Debug/libtest.so" ./code/test/a.o ./code/test/b.o -lpthread

现在编译器会遇到问题。它要生成libtest.so文件,需要上面的两个.o文件,可是我们还没有编译出来啊,怎么办?不用担心,下面就要编译.o文件了。
 
%.o:
 @echo 'Building file: $(subst $(FinalOutput),,$(@:.o=.cpp))'
 @echo 'Invoking:GCC C++ Compiler'
 g++ $(CFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$(subst $(FinalOutput),,$(@:.o=.cpp))"
 @echo 'Finished building: $(subst $(FinalOutput),,$(@:.o=.cpp))'
 @echo ' '
 
这一行——%.o:
其实是给.o文件定义了个规则。意思是说,make的时候,遇到的所有的.o文件,都按照这里定义的方式来生成,具体来说,就是
g++ $(CFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$(subst $(FinalOutput),,$(@:.o=.cpp))"
这行。

上面这行,就是调用g++,然后配合编译的参数,从.cpp文件,生成.o文件。在这行里面,我只解释一下$(subst $(FinalOutput),,$(@:.o=.cpp))。其他的各个参数,去看g++的手册好了。
subst是个系统函数,作用是进行字符串的替换。用法如下:
$(subst 要被替换的字符串,要用来替换的字符串,要参与替换活动的字符串集合)。
上面的代码,意思就是说,把字符串集合$(@:.o=.cpp)里面每个字符串的$(FinalOutput)部分(在例子里面,也就是bin/Debug/),都替换成——既然没有写,就相当于是删掉了。
而$(@:.o=.cpp)则是将目标里面的.o部分,替换成.cpp。
还用上面的a.cpp来举例说明。在编译成bin/Debug/code/test/a.o的时候,会去编译code/test/a.cpp。

结合上面的讲解,在生成最终目标之前,遇到的每个.o文件,都会按照上面的规则,首先把需要的所有的.o文件都编译出来,然后再进行链接,生成最终的目标文件。
 
#other targets
clean:
 -$(RM) $(Output)/*
 -@echo ' '
.PHONY:all clean
这里是配置的clean的内容,执行make clean的时候,会做这些事情。

好了。makefile的学习总结就写到这里

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章