make?le定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯, 甚至進行更復雜的功能操作,因為make?le 像一個Shell 本一樣,其中也可以執行操作系統的命令。
makefile規則
make?le的規則,也是makefile最核心的內容:
target ... : prerequisites ...
???? command
????...
????...
- target 可以是一個object ?le(目標文件),也可以是一個執行文件,還可以是一個標簽(label)。對于標簽這種特性,在后續的“偽目標” 節中會有敘述。
- prerequisites 生成該target 依賴的文件和/或 target
- command 該target要 行的命令(任意的shell命令)
prerequisites中如果有一個以上的文件比 target文件要新的話,command 定義的命令就會被執行。
在定義好依賴關系后,后續的那一行定義了如何生成目標文件的操作系統命令,一定要以一個Tab 鍵作為開頭
Makefile組成
Make?le 主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和注釋。
- 顯式規則。顯式規則說明了如何生成一個或者多個目標文件。這是由Make?le的書寫者明顯指出要生成的文件、文件的依賴文件和生成的命令。
- 隱晦規則。 由于make有自動推導的功能, 所以隱晦的規則可以讓我們比較簡單地書寫Make?le,這是 make所支持的。
- 變量定義。在Make?le中要定義一系列的變量,變量一般都是字符串,這個有點像C語言中的宏, Make?le被執行時,其中的變量都會被擴展到相應的引用位置上。
- 文件指示。其包含了三個部分,一個是在一個Make?le中引用另一個Make?le, 像C語言中的include一樣;另一個是指根據某些情況指定Make?le中的有效部分, 像C語言中的預編譯#if一樣;還有一個就是是定義一個多行的命令(后續編輯補充)。
- 注釋 。Make?le中只有行注釋,和UNIX的Shell腳步一樣,其注釋是 # 字 ,如果你要在你的Make?le中使 # 字 ,可以使用反斜框進行轉義 : \#。
最后還值得一提的是,在Make?le中的命令, 必須要以Tab鍵開始 。
Makefile工作方式
GNU的make工作時的執行步驟如下:
- 讀入所有的Make?le。
- 讀入被include的其它Make?le。
- 初始化文件中的變量。
- 推導隱晦規則,并分析所有規則。
- 為所有的目標文件創建依賴關系鏈。
- 根據依賴關系,決定哪些目標文件需要重新生成。
- 執行生成命令。
1-5 為第一個階段,6-7為第二個階段 。 第一個階段中, 如果定義的變量被使用了,那么make會把其展開在使用的位置。但make并不會完全馬上展開 ,make使用的是拖延戰術,如果變量出現在依賴關系的規則中,那么僅當這條依賴被決定要使用了,變量才會在其內部展開。
偽目標
正如下面例子中的“clean”一樣,既然我們生成了許多編譯文件, 也應該提供一個以清除它們為“目標”的target,以備以后完整地再次編譯(如以“make clean”來使用該目標)
clean:
????rm *.o temp
因為, 我們并不生成 “clean”這個文件。“偽目標”并不是一個文件,只是一個標簽, 由于“偽目標”不是文件, 所以make無法生成它的依賴關系和決定它是否要執行。 我們只有通過顯式地指明這個“目標” 能讓其生效。當然,“偽目標”的取名不能和文件名重名,不然其就去了“偽目標”的意義了。
為了避免和文件重名的這種情況, 我們可以使 一個特殊的標記 “.PHONY” 來顯式地指明一個目標是“偽目標”,向make說明,不管是否有這個文件,這個目標就是“偽目標”。
.PHONY : clean
clean:
????rm *.o temp
變量
變量在聲明時需要給予初值,而在使用時,需要在變量名前加上 $ 號,但是最好用小括號() 或者大括號 {} 將邊看給包括起來。 如果你要使用真實的 $ 字符,那么你需要使用 $$ 來表示。
objects = program.o foo.o
program : $(objects)
????cc -o program $(objects)
自動化變量
自動化變量會把模式中所定義的一系列的文件自動地挨個取出,直到所有的符合模式的文件都取完了。這種自動化變量只應出現在規則的命令中。
下面是所有的自動化變量及其說明:
- $@ : 表示規則中的目標文件集。在模式規則中,如果有多個目標,那么$@ 是匹配于目標中模式定義的集合。
- $% : 僅當目標是函數庫文件中,表示規則中的目標 員名。例如,如果一個目標是foo.a(bar.o) ,那么, $% 是 bar.o , $@ 是 foo.a 。如果目標不是函數庫文件(Unix下是.a,Windows下是 .lib),那么其值為空。
- $< : 依賴目標中的第一個目標名字。如果依賴目標是以模式(即 % )定義的,那么 $<將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
- $? : 所有比目標新的依賴目標的集合,以空格分隔。
- $^ : 有的依賴目標的集合,以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只報了一份。
- $+ : 這個變量像$^ ,也是所有依賴目標的集合。只是它不去除重復的依賴目標。
- $* : 這個變量表示目標 中 % 及其之前的部分。 果目標是 dir/a.foo.b ,并且目標的 是 a.%.b ,那么, $* 的值就是 dir/a.foo 。這個變量對于構造有關聯的文件名是比較有用。如果目標中沒有模式的定義,那么 $* 也就不能被推導出,但是,如果目標文件的后綴是make 識別的,那么 $* 是除了后綴的那一部分。例如: 如果目標是 foo.c ,因為 .c 是make 能識別的后綴名, 所以 $* 的值 是 foo 。這個特性是GNU make的, 很有可能不兼容于其它版本的make, 所以你應該盡量避免使 $* ,除非是在隱含規則或者是靜態模式中。 如果目標中的后綴是make 不能識別的,那么 $*就是空值。
在上述所列出來的自動化變量中。四個變量( $@ 、 $< 、 $% 、 $* )在擴展時只會有一個文件,而另三個的值是一個文件列表。這七個自動化變量還可以取得文件的目錄名或者是在當前目錄下符合模式的文件名,只需要加上 D 或者 F 字樣。。這是GNU make中老版本的特性,在新版本中使 用函數 dir 或者 notdir 就可以做到了。
make的運行
make命令執行后有三個退出碼:
0 表示成功執行。
1 如果make運行時出現任何錯誤,其返回1。
2 如果你使用了make的“-q”選 ,并且make使 一些目標不需要更新,那么返回2。
make的-debug[=<options>]選項, 輸出make的調試信息。它有幾種不同的級別可供選 , 如果沒有參數,
那 是輸出最簡單的調試信息。下面是<options>的取值:
? a: 也就是all,輸出所有的調試信息。(會非常的 )
? b: 也就是basic,只輸出簡單的調試信息。即輸出不需要重編譯的目標。
? v: 也就是verbose,在b選項的級別之上。輸出的信息包含哪個make?le被解析,不需要被重編譯的依賴文件(就是依賴目標)等。
? i: 也就是implicit,輸出所有的隱含規則。
? j: 也就是jobs,輸出執行規則中命令的詳細信息,如命令的PID、返回碼等。
? m: 也就是make?le,輸出make讀取make?le,更新make?le,執行make?le的信息。
TIPS
- (1) make支持三個通配符:* ,? 和~ 。這是和Unix的B-Shell是相同的。
通配符同樣可以使用在Makefile變量中,如下所示:
objects = *.o
但這并不是說 *.o 會展開 ,不!objects的值是 *.o 。Make?le中的變 其實 是C/C++中的宏。 如果你要讓通配符在變量中展開 ,也就是讓objects的值是所有 .o的文件名的集合,那么可以這樣:
objects := $(wildcard *.o)
- (2) GNU的C/C++編譯器都支持一個“-M”的選 ,即自動找尋源文件中包含的頭文件,并生成一個依賴關系, -M參數會 一些標準庫的頭文件也會包含進來。例如,如果我們執行下面的命令:
gcc -M sha1.c
sha1.o: sha1.c /usr/include/stdio.h /usr/include/features.h
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-64.h
/usr/lib/gcc/x86_64-redhat-linux/4.4.7/include/stddef.h
/usr/include/bits/types.h /usr/include/bits/typesizes.h
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h
/usr/lib/gcc/x86_64-redhat-linux/4.4.7/include/stdarg.h
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h
/usr/include/string.h /usr/include/xlocale.h /usr/include/stdint.h
/usr/include/bits/wchar.h sha1.h
如果使用 -MM 參數,則不會包含標準庫的頭文件,大多數其它c/c++編譯器使用-M參數即可:
gcc -MM sha1.c
sha1.o: sha1.c sha1.h
于是由編譯器自動生成的依賴關系,這樣一來,不必再手動書寫若干文件的依賴關系,而 編譯器自動生成了。
(3) make會將其要執行的命令行在命令執行前輸出到屏幕上。 當我們使用@字符在命令行前,那么這個命令便不會被make顯示出來。
(4)如果make執行時,帶入make參數 -n 或者 --just-print ,那么其只會顯示命令,但不會執行命令,這個功能很有利于我們調試Make?le,看看書寫的命令是執行起來是什么樣子的或是什么順序的。
而make參數-s 或者 --silent --quiet 則是全面禁止命令的顯示。
在“嵌套執行”中一個比較有用的參數, -w 或者 --print-directory 會在make的執行過程中輸出一些信息,讓你看到當前的工作目錄。(5)如果你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。 你的第一條命令是cd命令,你希望第二條命令是在cd之后的基礎上運行,那么你不能將這兩條命令寫在兩行上,而應該將這兩條命令寫在一行上, 分號分隔。例如:
示例1:
exec:
????cd /home/temp
????pwd
示例2:
exec:
????cd /home/temp; pwd
當我們執行 make exec時, 第一個例子中的cd沒有作用,pwd會打印出 當前的Make?le目錄,而第二個例子中,cd 起作用了,pwd會 打印出“/home/temp”。
- (6) 每當命令運行完后,make會檢測每個命令的返回碼, 如果命令返回成功,那么make會執行下一條命令, 規則中所有的命令成功返回后,這個規則算是成功完成了。如果一個規則中的某個命令出錯了(命令退出碼非零),那么make會終止執行當前規則,這將有可能終止所有規則的執行。
如果我們希望忽略命令的出錯,不影響以后的命令的執行,我們可以在Make?le的命令行前加一個減號 -(在Tab鍵之后),標記為不管命令出不出錯都認為是成功的。如:
clean:
????-rm *.o temp
例外給make加上 -i 或者 --ignore-errors 參數,那么Make?le中所有命令都會忽略錯誤。而如果一個規則是以 .IGNORE 作為目標的,那么這個規則中的所有命令都會忽略錯誤。這些是不同級別的防止命令出錯的方法,你可以根據你的不同喜好設置。
還有一個要提一下的make的參數的是 -k 或者 --keep-going ,這個參數的意思是:如果某個規則中的命令出錯了,那么終止該規則的執行,但繼續執行其它規則。
- (7)make的“隱含規則”功能會自動為我們去推導目標的依賴目標和生成命令。make會在自己的“隱含規則”庫中尋找可以使用的規則, 如果找到,那么就會使用。如果找不到,那么就會報錯。當然我們也可以使 make的參數 -r 或者 --no-builtin-rules 選項來取消 所有的預設置的隱含規則。