Makefile基礎理解
一、基本介紹
在一些大型工程中,包含有許許多多的原始檔,它們按照型別、功能、模組等分別放在若干個目錄中,如果我們一個一個的手動去編譯執行它們,這將會耗費大量的精力,這樣的工作量我們是無法想象的。為了解決這樣的問題,我們需要藉助 makefile 來進行自動編譯,那什麼是 makefile 呢?
makefile 其實就是一個檔案,在它的裡面定義了一系列的規則,用來指定哪些檔案需要先編譯,哪些檔案需要後編譯,哪些檔案需要重新編譯,甚至於進行更復雜的功能操作。因為 makefile 就像一個Shell指令碼一樣,所以其中也可以執行作業系統的命令。
當然只有 makefile 還是不夠的,我們還需要一個用來解釋 makefile 檔案中那些規則和命令的工具——make
二、makefile 的編寫規則
target ... :prerequisites ...
command
...
目標物件名稱:依賴物件名稱 [Tab]為了生成目標物件所執行的命令 [Tab]...
1.三要素
(1)target
target 是一個目標檔案,即需要由make工具建立的目標體,可以是Object File,也可以是執行檔案,還可以是一個標籤(Label)
(2)prerequisites
prerequisites 就是要生成那個target目標檔案所需要的依賴檔案
(3)command
command 就是make需要執行的命令(任意的Shell命令),即建立每個目標體時需要執行的命令 注意: 在每一個command前都必須加上一個[Tab](製表符),不允許為空格
2.說明
makefile 檔案其實就是一個檔案的依賴關係。也就是說,target這一個或多個的目標檔案依賴於prerequisites中的檔案,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的檔案比target檔案要新的話,command所定義的命令就會被執行
3.例項
C程式碼:
#include <stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
makefile檔案:
test:test.o
gcc test.o -o test
test.o:test.s
gcc -c test.s -o test.o
test.s:test.i
gcc -S test.i -o test.s
test.i:test.c
gcc -E test.c -o test.i
.PHONY:clean
clean:
rm -f test.i test.s test.o test
解釋: (1)上面最終的目標物件是一個執行檔案 test (2)依賴關係:
- test 依賴於 test.o
- test.o 依賴於 test.s
- test.s 依賴於 test.i
- test.i 依賴於 test.c
(3)依賴方法:
- gcc [-option] test.* [-option] test.*
對於這個makefile檔案,每一個目標物件都有一個依賴檔案,通過上面的依賴方法可依次生成每一個目標物件,從而最後生成我們的最終目標物件——test。 (4)對於上面的 .PHONY:clean,這是宣告一個偽目標clean,它的主要用途我們稍後再說。
三、make的工作方式
在預設方式下,當我們寫好makefile檔案後,只需要輸入make命令就可以完成整個C程式碼的編譯過程。就用上面的例子來說明:
[[email protected] workspace]$ make
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test
[[email protected] workspace]$
那麼make到底是如何工作的呢?
- 首先,make會在當前目錄下查詢檔名為 makefile 或 Makefile 的檔案;
- 如果找到,它會找檔案中的第一個目標物件,就好比上面例子中的 test,並把它當做最終的目標檔案;
- 如果 test 檔案不存在,或者是 test 所依賴的檔案 test.o 的修改時間要比 test 新,那麼make就會執行後面的命令 gcc test.o -o test 來生成 test 這個檔案;
- 如果 test 所依賴的檔案 test.o 也不存在,那麼make就會在當前目錄中找 test.o 的依賴檔案 test.s,根據所依賴的方法來生成 test.o;
- 以此類推,test.s 就會找 test.i ,test.i 就會找 test.c(這有點像一個堆疊的過程),當然 test.c 是肯定存在的,於是我們就可以依次生成每一個目標檔案,從而最後生成我們的最終目標執行檔案 test。
以上就是make的工作方式,make首先會通過依賴關係一層一層地去尋找要生成目標檔案所依賴的檔案,接著又通過依賴方法一層一層地去生成目標檔案,最終生成我們的第一個target。 在這裡我們還需要注意以下幾點: 1. 在尋找的過程中,如果出現錯誤,比如最後所依賴的檔案找不到了,那麼make會直接報錯退出;而對於所定義命令的錯誤或者是編譯失敗,make還是會正常執行; 2. 對於clean這個偽目標,它和我們的第一個目標檔案既沒有直接的關聯,也沒有間接的關聯,那麼它後面所定義的命令將不會被自動執行。不過,我們可以通過make顯式地去執行它,即通過命令 make clean ,以此來清除所有的目標檔案,這不僅便於我們重新編譯,也很利於保持檔案的清潔。
四、makefile的主要內容
如果有這樣一個工程,它有2個頭檔案(.h)和4個C檔案(.c),它的makefile如下所示:
test:test_1.o test_2.o test_3.o test_4.o
gcc -o test test_1.o test_2.o test_3.o test_4.o
test_1.o:test_1.c head_1.h
gcc -c test_1.c
test_2.o:test_2.c head_1.h
gcc -c test_2.c
test_3.o:test_3.c head_2.h
gcc -c test_3.c
test_4.o:test_4.c head_2.h
gcc -c test_4.c
.PHONY clean
claen:
rm -f test test test_1.o test_2.o test_3.o test_4.o
我們可以通過這個例子來一 一解釋makefile中的內容:
1. 顯式規則
顯式規則說明了如何生成一個或多個目標檔案,需要由makefile的書寫者明確指出三要素,即要生成的目標檔案、目標檔案所依賴的檔案以及生成的命令。
2. 隱晦規則
make有自動推導的功能,隱晦的規則可以讓我們比較簡略地書寫makefile。 那什麼是自動推導呢? GNU的make很強大,它可以自動推導檔案以及檔案依賴關係後面的命令,只要make看到一個 *.o檔案 ,它就會自動把 *.c檔案 加在依賴關係中,並且 gcc -c *.c 也會被推匯出來,於是我們就沒有必要在每一個 *.o檔案 後面都寫上類似的命令,因為我們的make會自動識別,並自己推導。 就拿上面的例子來說,它的makefile可以簡寫成如下的樣子:
test:test_1.o test_2.o test_3.o test_4.o
gcc -o test test_1.o test_2.o test_3.o test_4.o
test_1.o:head_1.h
test_2.o:head_1.h
test_3.o:head_2.h
test_4.o:head_2.h
.PHONY clean
claen:
rm -f test test test_1.o test_2.o test_3.o test_4.o
3. 變數的定義
對於上面的例子,我們可以看到 *.o檔案 的字串(test test_1.o test_2.o test_3.o test_4.o
)被重複了三次,如果我們的工程需要加入一個新的 *.o檔案 ,那麼我們需要在三個地方加上,當然,對於這個makefile來說,它其實還並不複雜。但如果我們有一個很大工程,它包含著數以萬計的檔案,那麼這個makefile將變得非常複雜,這時加入一個新的 *.o檔案 ,那麼我們就有可能會忘掉一個需要加入的地方,而導致編譯失敗。所以,為了makefile易維護,在makefile中我們可以使用變數。
在makefile中定義的一系列變數一般都是字串,這個有點像C語言中的巨集,當makefile被執行時,其中的變數都會被擴充套件到相應的引用位置上。
比如,我們宣告一個變數叫objects,我們在makefile一開始時就這樣定義:
objects = test test_1.o test_2.o test_3.o test_4.o
於是,我們就可以很方便的在我們的makefile中以 $(objects) 的方式來使用這個變量了,此時上面例子中的makefile就又可以簡寫成如下的樣子:
objects = test test_1.o test_2.o test_3.o test_4.o
test:$(objects)
gcc -o $(objects)
test_1.o:head_1.h
test_2.o:head_1.h
test_3.o:head_2.h
test_4.o:head_2.h
.PHONY clean
claen:
rm -f $(objects)
此時若有新的 *.o檔案 加入時,我們只需要簡單修改objects變數就可以了
4. 檔案指示
包括三個部分,一是在一個makefile中引用另一個makefile,就像C語言中的#include一樣;二是指根據某些情況指定makefile中的有效部分,就像C語言中的預編譯#if一樣;三是定義一個多行的命令。(這部分我會在之後說明)
5. 註釋
makefile中只有行註釋,和UNIX的Shell指令碼一樣,其註釋是用 # 字元,如果你要在你的makefile中使用 ‘#’ 字元,可以使用反斜槓進行轉義,如:\# 。
五、makefile的執行步驟
- 讀入所有的makefile
- 讀入被include的其它makefile
- 初始化檔案中的變數
- 推導隱晦規則,並分析所有規則
- 為所有的目標檔案建立依賴關係鏈
- 根據依賴關係,決定哪些目標要重新生成
- 執行生成命令
1 ~ 5步為第一個階段,6 ~ 7為第二個階段。在第一個階段中,如果定義的變數被使用了,那麼make會把其展開在使用的位置上。但是,make並不會完全馬上展開,只有當這個變量出現在某條依賴關係的規則中,而且這條依賴規則要被使用了,變數才會在其內部展開。