本節書摘來自異步社群《嵌入式linux開發實用教程》一書中的第1章,第1.2節,作者 朱兆祺,李強,袁晉蓉,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
makefile如今能得以廣泛應用,這還得歸功于它被包含在unix系統中。在make誕生之前,unix系統的編譯系統主要由“make”、“install”shell腳本程式和程式的源代碼組成。它可以把不同目标的指令組成一個檔案,而且可以抽象化依賴關系的檢查和存檔。這是向現代編譯環境發展的重要一步。1977年,斯圖亞特·費爾德曼在貝爾實驗室裡制作了這個軟體。2003年,斯圖亞特·費爾德曼因發明了這樣一個重要的工具而接受了美國計算機協會(acm)頒發的軟體系統獎。
makefile檔案可以實作自動化編譯,隻需要一個“make”指令,整個工程就能完全自動編譯,極大地提高了軟體開發的效率。目前雖有衆多依賴關系檢查工具,但是make是應用最廣泛的一個。一個程式員會不會寫makefile,從一個側面說明了這個程式員是否具備完成大型工程的能力。
一個簡單的makefile語句由目标、依賴條件、指令組成。
smdk6400_config:目标;
unconfig:先決條件;
@mkdir -p $(obj)include $(obj)board/samsung/smdk6400:指令。這裡特别注意,“@”前面是tab鍵,并且必須是tab鍵,而不能是空格。
目标和先決條件是依賴關系,目标是依賴于先決條件生成的。
1.變量的引用方式
使用“$(objtree)”或者“${ objtree }”來引用objtree這個變量的定義。這個引用方式似乎很像c語言中的指針變量,使用*p來取存放在指針p中的值。
$(if $(build_dir),$(build_dir),$(curdir))的含義:如果“build_dir”變量值不為空,則将變量“build_dir”指定的目錄作為一個子目錄;否則将目錄“curdir”作為一個子目錄。
2.遞歸展開式變量
這類變量的定義是通過“=”和“define”來定義的。
其優點是:這種類型遞歸展開式的變量在定義時,可以引用其他之前沒有定義過的變量,這個變量可能在後續部分定義,或者是通過make的指令行選項傳遞的變量來定義。
其缺點是:其一,使用此風格的變量定義,可能會由于出現變量的遞歸定義而導緻make陷入到無限的變量展開過程中,最終使make執行失敗。
這樣的話會使得makefile出錯,因為到最終引用了自己。
其二,這種風格的變量定義中如果使用了函數,那麼包含在變量值中的函數總會在變量被引用的地方執行。
3.直接展開式變量
為了避免遞歸展開式變量存在的問題和不友善,gnu make支援另外一種風格的變量,稱為直接展開式變量。這種風格的變量使用“:=”定義。在使用“:=”定義變量時,變量值中對其他變量或者函數的引用在定義變量時被展開,也就是對變量進行替換。
這裡的輸出是:teacher student。
這個直接展開式變量在定義時就完成了對所引用變量和函數的展開,是以不能實作對其後定義變量的引用。
4.條件指派
在對變量進行指派之前,會對其進行判斷,隻有在這個變量之前沒有進行指派的情況下才會對這個變量進行指派。
由于x在之前被指派了,是以這裡的輸出是student。
5.變量的替換引用
對于一個已經定義的變量,可以使用變量的替換引用将變量中的字尾字元使用指定的字元替換。格式為“$(x:a=b)”(或者“${x:a=b}”),即将變量“x”中所有以“a”字元結尾的字替換為以“b”結尾的字。
特别注意的是,$(x: .o=.c)的“=”兩邊不能有空格。這裡的輸出是:fun.o main.o fun.c main.c。
6.追加變量值
追加變量值是指一個通用變量在定義之後的其他一個地方,可以對其值進行追加。也就是說可以在定義時(也可以不定義而直接追加)給它賦一個基本值,後續根據需要可随時對它的值進行追加(增加它的值)。在makefile中使用“+=”(追加方式)來實作對一個變量值的追加操作。
這裡的輸出是:fun.o main.o sub.o。
1.ifneq關鍵字
這個關鍵字是用來判斷兩個參數是否不相等。格式為:
在判斷之前先要将value1和value2的值進行展開和替換,如在u-boot-2013.04的頂層目錄makefile中,對u-boot的版本參數就使用了ifneq關鍵字進行判斷。
先将sublevel使用$()展開和替換,如果sublevel的值不是空,則執行:
也就是說,如果$(sublevel) = 1的話,那麼u_boot_version = 2013.04.1。
如果sublevel的值是空,則執行:
那麼此時u_boot_version = 2013.04。
2.ifeq關鍵字
ifeq關鍵字和ifneq關鍵字是相對而言的,用來判斷兩個參數是否相等。格式為:
和ifneq一樣,先要将value1和value2展開替換之後,再進行比較。
如果hostarch展開替換之後和arch展開替換之後相等,則:
否則cross_compile不等于/usr/local/arm/4.4.1/bin/arm-linux-。
3.ifndef關鍵字
ifndef關鍵字用來判斷一個變量是否沒有進行定義。格式:
由于在makefile中,沒有定義的變量的值為空。
如果config_sandbox值為空,條件成立,執行如下語句:
否則不執行。
4.ifdef關鍵字
ifdef關鍵字用來判斷一個變量是否已經進行定義過。格式:
如:
如果config_sys_ldscript定義過,則執行:
1.makefile函數文法
在makefile中,函數的調用和變量的調用類似,都是使用“$”進行辨別。文法如下:
函數名與函數的參數之間使用空格隔開,而函數的參數間使用逗号進行分隔。以上兩種寫法都是可以的,但是為了風格統一,請不要兩者進行混合使用。
2.shell函數
make可以使用shell函數和外部通信。shell函數本身的傳回值是其參數的執行結果,沒有進行任何處理,對結果的處理是由make進行的。當對函數的引用出現在規則的指令行中,指令行在執行時函數才被展開。展開時函數參數(shell指令)的執行是在另外一個shell程序中完成的,是以需要對出現在規則指令行的多級“shell”函數引用需要謹慎處理,否則會影響效率(每一級的“shell”函數的參數都會有各自的shell程序)。
建立一個測試程式,makefile的内容:
func檔案中的内容:
執行完成makefile之後:
在u-boot和linux核心源碼中将會大量使用到shell函數。
3.subst函數
subst函數是字元串替換函數,文法為:
執行subst函數之後,傳回的是執行替換操作之後的字元串。如下:
執行上面的makefile,輸出結果為:
即将“z”替換成了“z”。
4.dir函數
dir函數作用為取出該檔案的目錄,其文法為:
執行該函數之後傳回檔案目錄部分。
makefile中常用函數較多,筆者就不一一例舉了,讀者可參考相關書籍進行深入了解。