1. 程式人生 > >基於gsoap示例分析來學習makefile

基於gsoap示例分析來學習makefile

這裡寫圖片描述

來還債和努力不能打臉,所以今天學makefile去了~以下是基礎筆記和針對昨天gsoap的demo中makefile檔案涉及到的用法的記錄

目錄

1. makefile基礎用法

參考文章:makefile簡易教程
(心似野馬……陳皓大叔寫的博文一開始居然沒能看下去,我覺得肯定是排版的問題……)

筆記
看懂了兩點:
1、makefile的規則很直白

target ... : prerequisites ...
    command
    ....
    ....

就是prerequisites 指明輸入(依賴項),target是輸出,command是實現。
模式規則中,規則的目標和依賴檔名代表了一類檔名,規則的命令是對所有這一類檔案重建過程的描述。
2、有很多基礎保命必備偷懶方法
隨著應用場景複雜度增加,有很多可以方法可以寫的更少,效率更高。makefile支援模式匹配和使用函式來提高可擴充套件性。

2. 語法講解

參考文章:跟我一起寫makefile
排版很棒,所以過了一遍。(假裝看完一遍就可以牛逼地消化掉的樣子,走開……)

3. gsoap示例中makefile檔案用到的語法說明

貼出makefile來湊字數和掩飾初老症狀之記憶力不好

RM := rm -rf

# 引入相關的生成檔案
-include sources.mk
-include subdir.mk
-include soap/subdir.mk
-include objects.mk

ifneq ($(MAKECMDGOALS),clean)
  ifneq ($(strip $(C++_DEPS)),)
    -include
$(C++_DEPS) endif ifneq ($(strip $(C_DEPS)),) -include $(C_DEPS) endif ifneq ($(strip $(CC_DEPS)),) -include $(CC_DEPS) endif ifneq ($(strip $(SERVER_CPP_DEPS)),) -include $(SERVER_CPP_DEPS) endif ifneq ($(strip $(CLIENT_CPP_DEPS)),) -include $(CLIENT_CPP_DEPS) endif ifneq ($(strip $(CXX_DEPS)),) -include $(CXX_DEPS) endif ifneq ($(strip $(C_UPPER_DEPS)),) -include $(C_UPPER_DEPS) endif endif # 生成目標 all: server client # 相關工具 server: $(SERVER_OBJS) $(USER_OBJS) @echo '建立目標: [email protected]' g++ -o"server" $(SERVER_OBJS) $(USER_OBJS) $(LIBS) @echo '完成建立目標: [email protected]' @echo "\n" client: $(CLIENT_OBJS) $(USER_OBJS) @echo '建立目標: [email protected]' g++ -o"client" $(CLIENT_OBJS) $(USER_OBJS) $(LIBS) @echo '完成建立目標: [email protected]' @echo "\n" # 清空臨時檔案 clean: -$(RM) $(SERVER_OBJS)$(CLIENT_OBJS)$(C++_DEPS)$(C_DEPS)$(CC_DEPS)$(SERVER_CPP_DEPS)$(CLIENT_CPP_DEPS)$(EXECUTABLES)$(CXX_DEPS)$(C_UPPER_DEPS) -@echo "\n" cleanall: -$(RM) $(SERVER_OBJS)$(CLIENT_OBJS)$(C++_DEPS)$(C_DEPS)$(CC_DEPS)$(SERVER_CPP_DEPS)$(CLIENT_CPP_DEPS)$(EXECUTABLES)$(CXX_DEPS)$(C_UPPER_DEPS) server client -@echo "\n" cleanexe: -$(RM) server client -@echo "\n" .PHONY: all clean dependents cleanall cleanexe
3.1. 變數定義

RM := rm -rf
makefile中的變數相當於巨集。變數引用方式$(變數名)
定義變數的幾種方式:
= :一般定義
:=:可以避免遞迴危險的變數定義方式,使用變數對當前變數進行賦值時,只能使用已經定義好的變數。

y := $(x) bar
x := foo

# y的值為bar

?=:如果變數之前沒有被定義過,那麼變數的值就被定義。如果變數的值之前已經被定義過了,這賦值語句什麼也不做

a := hello
b ?= world
a ?= miao
# a的值為hello

+=:給變數追加值,如果變數之前沒有定義過,則相當於=

objects = main.o foo.o bar.o utils.o
objects += another.o
3.2. 引用其他的makefile

include關鍵字可以把別的makefile包含進來,被包含的檔案會原模原樣地放在當前檔案的包含位置。
makefile會在一些規則前加上符號:
-:發生錯誤時makefile繼續。
@:命令不回顯。
+:使命令可以通過指定-n、-q或-t選項來執行。

3.3. 條件判斷

條件判斷的語法有兩類:

# type1
<conditional-directive>
    <text-if-true>
endif

# type2
<conditional-directive>
    <text-if-true>
else
    <text-if-false>
endif

條件關鍵字:
ifeq:判斷值相等。
ifneq:判斷值不相等。
ifdef:判斷變數是否定義。
ifndef:判斷變數是否未定義。

3.4. strip函式

makefile中函式呼叫的語法為:

$(<function> <arguments>)

make支援的函式不多,只在此列出,用到時查詢。

函式簽名 功能
$(subst ,,) 模式字串替換
$(patsubst ,,) 模式字串替換
$(findstring ,) 查詢字串
$(strip ) 去掉空格
$(filter ,) 過濾函式
$(filter-out ,) 反過濾函式
$(sort ) 排序函式
$(word ,) 取單詞
$(wordlist ,,) 取單詞串
$(words ) 單詞個數統計
$(firstword ) 取首單詞
$(dir ) 取目錄
$(notdir ) 取檔案
$(suffix ) 取字尾
$(basename ) 取字首
$(addsuffix ,) 加字尾
$(addprefix ,) 加字首
$(join ,) 連線函式
$(foreach ,,) 迴圈遍歷
$(if ,) 條件判斷
$(call ,,,,…) 建立引數化函式
$(origin ;) 返回變數資訊

至此,makefile中這一段的意思就明白了。

-include sources.mk
-include subdir.mk
-include soap/subdir.mk
-include objects.mk

ifneq ($(MAKECMDGOALS),clean)
  ifneq ($(strip $(C++_DEPS)),)
    -include $(C++_DEPS)
  endif
  ifneq ($(strip $(C_DEPS)),)
    -include $(C_DEPS)
  endif
  ifneq ($(strip $(CC_DEPS)),)
    -include $(CC_DEPS)
  endif
  ifneq ($(strip $(SERVER_CPP_DEPS)),)
    -include $(SERVER_CPP_DEPS)
  endif
  ifneq ($(strip $(CLIENT_CPP_DEPS)),)
    -include $(CLIENT_CPP_DEPS)
  endif
  ifneq ($(strip $(CXX_DEPS)),)
    -include $(CXX_DEPS)
  endif
  ifneq ($(strip $(C_UPPER_DEPS)),)
    -include $(C_UPPER_DEPS)
  endif
endif

主要就是為了引入變數,這一段使用的變數基本都是在sources.mk中定義的。

3.5. make工作方式和偽目標
  • 在當前目錄下找名字叫“Makefile”或“makefile”的檔案。
    如果找到,它會找檔案中的第一個目標檔案(target)並把這個檔案作為最終的目標檔案。
  • 如果edit檔案不存在,或是edit所依賴的後面的 .o 檔案的檔案修改時間要比目標檔案新,則會執行後面所定義的命令來生成edit這個檔案。
  • 如果edit所依賴的.o檔案也不存在,那麼make會在當前檔案中找目標為.o檔案的依賴性,如果找到則再根據那一個規則生成.o檔案。

可以看到makefile中第一個target是all,它依賴於server和client,make會繼續去生成server和client來最終生成目標檔案all。

# 生成目標
all: server client

# 相關工具
server: $(SERVER_OBJS) $(USER_OBJS)
    @echo '建立目標: [email protected]'
    g++  -o"server" $(SERVER_OBJS) $(USER_OBJS) $(LIBS)
    @echo '完成建立目標: [email protected]'
    @echo "\n"

client: $(CLIENT_OBJS) $(USER_OBJS)
    @echo '建立目標: [email protected]'
    g++  -o"client" $(CLIENT_OBJS) $(USER_OBJS) $(LIBS)
    @echo '完成建立目標: [email protected]'
    @echo "\n"
...

but,俺們並沒有看到all這個東西生出來~
當然是本著偷懶原則,為了少敲一個make,一次性把server和client都生出來。利用makefile中偽目標的特性,用關鍵字.PHONY來指定。
make不會為偽目標生成檔案,一般也不指定依賴,主要是為了執行command部分。
這裡將all這個偽目標指定為預設目標(最終目標),這樣就會進入上面介紹的make的工作方式的流程,檢查依賴,於是就保證server和client倆鐵球同批次出生咯~

.PHONY: all clean dependents cleanall cleanexe

其他的部分就是清理過程檔案。寫了好幾個檔案主要也是為了結構上更加方便。定義了很多變數都沒用上,demo辣麼小,炫makefile實在是挪不出地方來,不過這個思路是很好的,雖然看上去囉嗦了些,但是結構十分清楚。
至此,大體是怎麼來的基本就清楚了。

3.6. 靜態模式規則和自動化變數

在subdir.mk中有這樣的寫法:

# subdir.mk
%.o: ../%.cpp
    @echo '建立目標: $<'
    g++ -O0 -g3 -Wall -DWITH_NONAMESPACES -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"[email protected]" "$<"

其中%.o: ../%.cpp用到的就是靜態模式,其含義就是定義了同../路徑下的所有.cpp檔名稱相同的.o檔案。去掉路徑干擾就是%.o: %.cpp。核心的就是這個%,自行查詢該萬用字元的用法。
應用到多目標上,可以這樣寫:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o [email protected]

等價於下面的寫法

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

再抽象一層,就是靜態模式的規則語法啦:

<targets ...>: <target-pattern>: <prereq-patterns ...>
    <commands>

其中<target-pattern><prereq-patterns ...>定義的分別就是目標集(輸出)模式和 目標依賴(輸入)的模式,可以使用萬用字元哦~

此外,subdir.mk中還使用了[email protected]$<,這是makefile中的自動化變數。

要將規則從對一個檔案的操作提升為對一類檔案的操作,就需要保證規則中不使用具體名稱。規則的目標和依賴可以通過變數定義和靜態模式來與具體檔名稱解耦,而自動化變數就是讓command中也可以不適用具體名稱。

[email protected]
表示規則中的目標檔案集。在模式規則中,如果有多個目標,那麼,”[email protected]”就是匹配於目標中模式定義的集合。
$<
依賴目標中的第一個目標名字。如果依賴目標是以模式(即”%”)定義的,那麼”$<”將是符合模式的一系列的檔案集。注意,其是一個一個取出來的。

還有其他的,後面用到再查咯~

3.7. 奇怪的.d檔案

檢視makefile的時候發現會用到.d檔案,眯著眼睛搜尋了一下發現確實生成了.d檔案,但是又沒有發現哪裡生成的.d檔案。
如果有這樣的疑問,說明你是個細於觀察的人(肯定是沒有好好看語法講解部分的連結的文章!!!)

一般的示例中給出的依賴關係比較簡單,對於依賴部分的寫法不太能引起注意,像這樣:

main.o : main.c defs.h

當依賴十分複雜,需要很多.h和.c,一般.c可以通過定義變數和靜態模式規則來引入。但是標頭檔案.h就比較麻煩,跟target聯絡並不大,此時就需要手動一個個新增target所以來的標頭檔案,十分麻煩而且容易出錯。
.d檔案是基於一個基本認識:編譯器可以自動找尋原始檔中包含的標頭檔案,並生成依賴關係:

cc -M main.c   //輸出 main.o : main.c defs.h
// 或
gcc -MM main.c  // 輸出 main.o: main.c defs.h

:一般推薦使用gcc -MM 而不是 gcc -M。因為-M會把一些標準庫的標頭檔案也包含進來。
基於編譯器的這個功能,可以將依賴匯出,然後在makefile使用,這樣就方便很多啦~官方建議為每一個.c檔案都生成一個同名的.d檔案來存放其依賴關係。這就是.d檔案的來源啦。生成方式如下:

%.d: %.c
    @set -e; rm -f [email protected]; \
    $(CC) -M $(CPPFLAGS) $< > [email protected].$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o [email protected] : ,g' < [email protected].$$$$ > [email protected]; \
    rm -f [email protected].$$$$

:對於規則中的每個命令,make都是在一個新的shell上執行的,所以通過換行列出的命令之間的先後順序並非就是書寫順序。如果想要多個命令在同一個shell中執行按順序執行,則需要使用;將命令連起來,寫在同一行,可以使用\將不同的行連成一行。

以上便是奇怪的.d檔案的來源,回到這個demo的makefile中找一下生成.d的指令碼。

%.o: ../%.cpp
    @echo '建立目標: $<'
    g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"[email protected]" "$<"

這裡直接從.cpp到.o一步處理完了……
懵逼,木有按套路出牌啊!所以接下來其實是g++的主場。
在此之前先撇開g++引數的定義來看一下引數的內容,這是makefile提供的。
-MF-MT內容均為$(@:%.o=%.d),看著懵,其實就是一個變數$(變數名),變數名@:%.o=%d表示的就是將[email protected]中所有.o檔案同名的.d檔案。

下面是從make的列印資訊裡摳出來client的這一條語句展開的樣子:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"client.d" -MT"client.d" -o"client.o" "../client.cpp"

你看你看,分析對了吧~(起飛)

3.8. g++引數-MMD、-MF、-MT

參考連結:GCC常用引數說明
連結裡面講的比較清楚,有操作示例的貼圖。我自己也手動測了一下,整理用到的命令如下:

引數 功能
-MMD 將目標檔案的依賴關係匯出到同名的.d檔案中,匯出.d檔案的名稱可以通過-MF來修改。功能和-MM一樣,獲取的依賴中不包含標準庫的標頭檔案。
-MP 為依賴規則中的所有.h依賴項生成一個偽目標。該偽規則將避免刪除了對應的標頭檔案而沒有更新 “Makefile” 去匹配新的依賴關係而導致make出錯的情況出現。
-MF 指定用來存放依賴關係的檔案
-MT 指定依賴關係檔案中的目標名

需要注意的是-MT,其定義的是依賴關係檔案的內容中依賴的目標名,而不是依賴檔案的目標名。

這裡寫圖片描述

注意,這個與我們實際需要的.d檔案的內容還是不一樣的。我們需要的是把.d檔案也加到target中的效果:

main.o main.d : main.c defs.h

參考GCC手冊中對-MT的講解:

-MT target
Change the target of the rule emitted by dependency generation. By default CPP takes the name of the main input file, deletes any directory components and any file suffix such as ‘.c’, and appends the platform’s usual object suffix. The result is the target.
An -MT option sets the target to be exactly the string you specify. If you want multiple targets, you can specify them as a single argument to -MT, or use multiple -MT options.
For example, -MT '$(objpfx)foo.o'might give

$(objpfx)foo.o: foo.c

當需要指定多個target時,用""包起來的形式即可。再回去看makefile中的寫法,正是如此:

%.o: ../%.cpp
    g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"[email protected]" "$<"

至此,基本搞清楚它在說什麼了。
似乎結束了的樣子……
但是,回到剛剛摳出來的make的列印資訊

g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"client.d" -MT"client.d" -o"client.o" "../client.cpp"

並沒有解析成-MF"client.d client.o"這樣的形式,最後生成的.d檔案中卻是

client.d client.o: client.cpp soap/soapcalcProxy.h soap/soapH.h \
 soap/soapStub.h soap/calc.nsmap

對比g++,區別在於是否明確指定-o,這一點在g++手冊中沒有看到對應的說明,但是測試得出明確指定-o或者沒有指定-MT時,都會在依賴檔案中的target加上與目標檔案同名的.o。
待解釋

小結

需求驅動學習,【寫完bug改bug之歌】響起來~