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的學習總結就寫到這裏

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