1. 程式人生 > >【linux】-Makefile簡要知識+一個通用Makefile

【linux】-Makefile簡要知識+一個通用Makefile

[toc] --- ## Makefile  meke命令一般用於編譯程式,而make命令都依賴於 Makefile 檔案。  最簡單的Makefile如下: ```Makefile hello: hello.c gcc -o hello hello.c clean: rm -f hello ```  **注:縮排使用 tab 鍵 , 因為Makefile 會為每個以 tab 鍵開頭的命令建立一個 shell 去執行。**  具體規則參考 **《GUN Make 使用手冊》** *以下為粗略筆記* ### Makefile規則與示例 #### 為什麼需要Makefile  提高編譯效率。 #### Makefile樣式  一個簡單的Makefile檔案包含一系列"規則": ```makefile 目標...:依賴... 命令 ``` * 命令被執行的兩個簡單條件: * 目標檔案還沒有生成 * 依賴檔案比目標檔案新 #### 先介紹Makefile的兩個函式 1. $(foreach var,list,text)  for each var in list,change it to text。  對list中的每一個元素,取出賦值給var,然後把var改為text所描述的形式。 例子: ```makefile objs := a.o b.o dep_files := $(foreach f, $(objs), .$(f).d) // 最終 dep_files := .a.o.d .b.o.d ``` 2. $(wildcard pattern)   把存在的 pattern 檔案列出來 例子: ```makefile src_files := $(wildcard *.c) ``` #### 完善Makefile 1. 先來一個簡單粗暴、效率低的: ```makefile test: main.c hello.c hello.h gcc -o test main.c hello.c ``` 2. 再來一個Makefile,效率高、精煉,支援自動檢測標頭檔案 ```makefile objs := main.o hello.o test : $(objs) gcc -o test $^ # 需要判斷是否存在依賴檔案 # .main.o.d .hello.o.d dep_files := $(foreach f, $(objs), .$(f).d) dep_files := $(wildcard $(dep_files)) # 把依賴檔案包含進來 ifneq ($(dep_files),) include $(dep_files) endif %.o : %.c gcc -Wp,-MD,[email protected] -c -o $@ $< clean: rm *.o test -f distclean: rm $(dep_files) *.o test -f ``` 瞭解自動化變數。 #### 通用Makefile的使用 參考linux核心的Makefile來編寫一個通用的Makefile,特點: 1. 支援多個目錄、多層目錄、多個檔案; 2. 支援給所有檔案設定編譯選項; 3. 支援給目錄設定編譯選項; 4. 支援給某個檔案單獨設定編譯選項; 5. 簡單、好用。 #### 通用的Makefile解釋 ##### 零星知識點 ###### make命令的使用  執行**make**命令時,它會去當前目錄下查詢名為**Makefile**的檔案,並根據它的只是去執行操作,生成第一個目標。  也可以用 **-f** 選項指定檔案,如: ```shell make -f Makefile.build ``` *指定檔案的名字隨意定義*。 ---  可以使用 **-C** 指定目錄,切換到其他目錄去,如: ```shell make -C a/ abc.def ```  可以指定目標,不再預設生成第一個目錄,如: ```shell make -C a/ abc.def other_target ``` ###### 即時變數與臨時變數  變數定義語法: |形式|說明| |:-:|:-:| |A = xxx|延時變數| |B ?= xxx|延時變數,只有第一次定義時賦值才成功,若曾被定義過,則此賦值無效。| |C := xxx|立即變數| |D += yyy|如果D在前面是延時變數,那麼現在它還是**延時變數**
如果D在前面是立即變數,那麼它現在還是**立即變數**| **延時變數**:  使用時才確定該值。 如: ```makefiel A = $@ tets: @echo $A ``` 輸出 A 的值為 **test**。 **即時變數**:  定義時立即確定該值。 如: ```makefiel A := $@ tets: @echo $A ``` 輸出 A 的值為 **空**。 ###### 變數的匯出(export)  **export** 是供給子目錄的 **Makefile** 使用的(即 sub-make),同一級的makefile是訪問不到的。  可以通過makefile中的內建變數**MAKELEVEL**可以檢視當前的makefile的level。 ###### Makefile中的shell  Makefile中可以使用shell命令,如: ```makefile TOPDIR := $(shell_pwd) ``` ###### Makefile中放置第一個命令  執行目標時,如果不指定目標,則預設執行第一個目標。 所以,**第一個目標**的位置很重要。有時候不太方便把第一個目標完整地放在檔案的前面,這時可以在檔案的前面直接放置目標,在後面再完善它的依賴和命令。比如: ```makefile First_target: // 這句話放在前面 ........ // 其他程式碼,比如include其它檔案得到後的 xxx 變數 First_target : $(xxx) $(xxx) command ``` ###### 假想目標 Makefile中可能會有這樣的目標: ```makefile clean: rm -f $(shell find -name "*.o") rm -f $(TARGET) ``` **注**:如果當前目錄下更好有個名為“**clean**”的檔案,那麼執行“make clean”時就不會執行makefile中目標clean的內容,所以要把**clean**設定為假想目標即可: ```makefile .PHONY : clean ``` ###### 小知識 **PHONY** 是 phoney,即偽造的意思。**PHONY**後面的target是偽造的target,不是真實存在的檔案。同時,注意**make**命令中後面的target預設是檔案。 ###### 常用函式 * **$(foreach var, list, text)** for each var in list,change it to text. 直接上例子: ```shell objs := a.o b.o dep_files := $(foreach f, $(objs), .$(f).d) // dep_files 的最終結果為“**.a.o.d .b.o.d**” ``` --- * **$(wildcard pattern)** 列出 pattern 存在的檔案。 例子: ```shell src_files = $(wildcard *.c) // src_files的值為當前目錄下所有 .c 檔案。 ``` --- * **$(filter pattern..., text)** 把**text**中符合**pattern**格式的內容**留下來**。 ```shell obj-y := a.o b.o c/ d/ DIR := $(filter %/, $(obj-y)) //結果為:c/ d/ ``` --- * **$(filter-out pattern..., text)** 把**text**中符合**pattern**格式的內容**刪除掉**。 ```shell obj-y := a.o b.o c/ d/ DIR := $(filter-out %/, $(obj-y)) // 結果為:a.o b.o ``` --- * **$(patsubst pattern, replacement, text)** 尋找**text**中符合**pattern**格式的內容,用**replacement**代替他們。 ```shell subdir-y := a.o b.o c/ d/ subdir := $(patsubst %/, %, $(obj-y)) // 結果為:c d ``` --- ##### *通用Makefile設計思想 1. **在Makefile檔案中確定要編譯的檔案、目錄**,比如: ```makefile obj-y += main.o obj-y += a/ ``` ***Makefile 檔案總是被 Makefile.build 包含的。*** 2. **在 Makefile.build 中設定編譯規則,有 3 條編譯規則**: 1. 怎麼編譯子目錄? 1. 進入子目錄編譯即可: ```makefile $(subdir-y): make -C $@ -f $(TOPDIR)/Makefile.build ``` 2. 如何編譯當前目錄中的目標檔案? ```makefile %.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $< ``` 3. 當前目錄下的 **.o** 和子目錄下的 **built-in.o** 要打包起來: ```makefile $(TARGET) : built-in.o $(CC) $(LDFLAGS) -o $(TARGET) built-in.o ``` **思想總結**: 1. Makefile檔案確定要編譯的檔案和目錄 2. Makefile.build檔案包含規則 3. 每個需要編譯的子目錄都放個Makefile檔案,把需要編譯的檔案編譯成**built-in.o** 4. 再把所有子目錄的**built-in.o**連結到頂層的**built-in.o** 5. 最後把頂層**built-in.o**連結到**APP** ***最好重新分析一下通用Makefile*** ## 一個通用Makefile ### 本程式的Makefile分為3類: 1. 頂層目錄的Makefile 2. 頂層目錄的Makefile.build 3. 各級子目錄的Makefile ### 原始碼(註釋版) #### Makefile ```makefile CROSS_COMPILE = # 交叉編譯工具頭,如:arm-linux-gnueabihf- AS = $(CROSS_COMPILE)as # 把彙編檔案生成目標檔案 LD = $(CROSS_COMPILE)ld # 連結器,為前面生成的目的碼分配地址空間,將多個目標檔案連結成一個庫或者一個可執行檔案 CC = $(CROSS_COMPILE)gcc # 編譯器,對 C 原始檔進行編譯處理,生成彙編檔案 CPP = $(CC) -E AR = $(CROSS_COMPILE)ar # 打包器,用於庫操作,可以通過該工具從一個庫中刪除或則增加目的碼模組 NM = $(CROSS_COMPILE)nm # 檢視靜態庫檔案中的符號表 STRIP = $(CROSS_COMPILE)strip # 以最終生成的可執行檔案或者庫檔案作為輸入,然後消除掉其中的原始碼 OBJCOPY = $(CROSS_COMPILE)objcopy # 複製一個目標檔案的內容到另一個檔案中,可用於不同原始檔之間的格式轉換 OBJDUMP = $(CROSS_COMPILE)objdump # 檢視靜態庫或則動態庫的簽名方法 # 共享到sub-Makefile export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP # -Wall : 允許發出 GCC 提供的所有有用的報警資訊 # -O2 : “-On”優化等級 # -g : 在可執行程式中包含標準除錯資訊 # -I : 指定標頭檔案路徑(可多個) CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include # LDFLAGS是告訴連結器從哪裡尋找庫檔案,這在本Makefile是連結最後應用程式時的連結選項。 LDFLAGS := # 共享到sub-Makefile export CFLAGS LDFLAGS # 頂層路徑 TOPDIR := $(shell pwd) export TOPDIR # 最終目標 TARGET := test # 本次整個編譯需要源 檔案 和 目錄 # 這裡的“obj-y”是自己定義的一個格式,和“STRIP”這些一樣,*但是 一般核心會蒐集 ”obj-”的變數* obj-y += main.o # 需要把當前目錄下的 main.c 編程序序裡 obj-y += sub.o # 需要把當前目錄下的 sub.c 編程序序裡 obj-y += subdir/ # 需要進入 subdir 這個子目錄去尋找檔案來編程序序裡,具體是哪些檔案,由 subdir 目錄下的 Makefile 決定。 #obj-y += $(patsubst %.c,%.o,$(shell ls *.c)) # 第一個目標 all : start_recursive_build $(TARGET) @echo $(TARGET) has been built ! # 處理第一個依賴,**轉到 Makefile.build 執行** start_recursive_build: make -C ./ -f $(TOPDIR)/Makefile.build # 處理最終目標,把前期處理得出的 built-in.o 用上 $(TARGET) : built-in.o $(CC) -o $(TARGET) built-in.o $(LDFLAGS) # 清理 clean: rm -f $(shell find -name "*.o") rm -f $(TARGET) # 徹底清理 distclean: rm -f $(shell find -name "*.o") rm -f $(shell find -name "*.d") rm -f $(TARGET) ``` #### Makefile.build ***注意,include 命令,相對路徑為 執行本 Makefile.build 的路徑*** ```makefile # 偽目標 PHONY := __build __build: # 清空需要的變數 obj-y := subdir-y := EXTRA_CFLAGS := # 包含同級目錄Makefile # 這裡要注意,相對路徑為 執行本 Makefile.build 的路徑 include Makefile # 獲取當前 Makefile 需要編譯的子目錄的目錄名 # obj-y := a.o b.o c/ d/ # $(filter %/, $(obj-y)) : c/ d/ # __subdir-y : c d # subdir-y : c d __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y) # 把子目錄的目標定為以下注釋 # built-in.o d/built-in.o subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) # 獲取當前目錄需要編程序序的檔名作為,並寫為目標 # a.o b.o cur_objs := $(filter-out %/, $(obj-y)) # 使修改標頭檔案 .h 後,重新make後可以重新編譯(重要) dep_files := $(foreach f,$(cur_objs),.$(f).d) # 列出存在的檔案 dep_files := $(wildcard $(dep_files)) ifneq ($(dep_files),) include $(dep_files) endif PHONY += $(subdir-y) # 第一個目標 __build : $(subdir-y) built-in.o # 優先編譯 子目錄的內容 $(subdir-y): make -C $@ -f $(TOPDIR)/Makefile.build # 連結成 目標 built-in.o : $(cur_objs) $(subdir_objs) $(LD) -r -o $@ $^ dep_file = [email protected] # 生成 cur_objs 目標 %.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $< .PHONY : $(PHONY) ``` ### 使用說明 * 本程式的Makefile分為3類: 1. 頂層目錄的 Makefile 2. 頂層目錄的 Makefile.build 3. 各級子目錄的 Makefile #### 1. 各級子目錄的Makefile * 參考: ```makefile # 可以不新增下面兩個變數 EXTRA_CFLAGS := CFLAGS_test.o := obj-y += test.o obj-y += subdir/ ``` * **obj-y += file.o** 表示把當前目錄下的file.c編程序序裡, * **obj-y += subdir/** 表示要進入subdir這個子目錄下去尋找檔案來編程序序裡,是哪些檔案由subdir目錄下的Makefile決定。 * **EXTRA_CFLAGS**, 它給當前目錄下的所有檔案(不含其下的子目錄)設定額外的編譯選項, 可以不設定 * **CFLAGS_xxx.o**, 它給當前目錄下的xxx.c設定它自己的編譯選項, 可以不設定 ##### 注意 1. **"subdir/"中的斜槓"/"不可省略** 2. **頂層Makefile中的CFLAGS在編譯任意一個.c檔案時都會使用** 3. **CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者組成xxx.c的編譯選項** #### 2. 頂層目錄的Makefile 主要作用: * 整個工程的引數初期定義 * 架構 * 工具鏈 * 編譯工具 * 編譯引數 * 需要匯出的變數 * 等等 * 定義最終目標 * 跳轉到 **Makefile.build** #### 3. 頂層目錄的Makefile.build ** **注意:該檔案雖然放在頂層,但是也是提供給各級子目錄使用的。** 主要功能: * 把某個目錄及它的所有子目錄中、需要編程序序去的檔案都編譯出來,把各個**subdir/built-in.o**和當前目錄的目標 **.o** 合併打包為當前目錄的**built-in.o** 。 #### 使用提示 * 執行"make"來編譯,執行 **make clean** 來清除,執行 **make distclean** 來徹底清除。 ##### Makefile附件 ###### 一些符號 |符號|說明| |:-:|:-:| |$@|表示規則中的目標檔案集| |$%|當目標為函式庫的時候,則表示規則中的目標成員名。反之為空。如一個目標為"*foo.a(bar.o)*",那麼,"$%"就是"bar.o",以空格分隔開。| |$<|依賴檔案集合中的第一個檔案,如果依賴檔案以"*%*"形式出現,則表示符合模式的一系列檔案集合| |$?|所有比目標新的依賴集合,以空格分隔開。| |$^|所有依賴檔案集合,以空格分隔開。如果依賴有相同,則取其一。| |$+|和 "$^"類同,但是不會把相同的刪除掉。| |$* |這個變量表示目標模式中 "%"及其之前的部分,如果目標是 test/a.test.c,目標模式為 a.%.c, 那麼 "$* " 就是 test/a.test。| ## 參考 * 以上筆記為整理韋東山老師筆記