makefile就是一個可以被make命令解析的檔案,他定義了一系列編譯的規則,幫助我們更加方便、簡潔的去完成編譯的過程。在一個大工程當中我們會有各種各樣的檔案,我們可能會分模組去存放各種檔案,可能有些檔案還依賴其他的檔案,因此我們在編譯的時候需要先將被依賴的檔案先編譯,其他檔案後編譯,而我們使用makefile就可以更好的去完成這件事兒。
在很多情況下我們在C/C++的專案當中使用makefile,其實在其他語言的專案也可以使用make和makefile,它不侷限於語言的,可以是C也可以是Java。現在寫一個基本的例子:
demo: demo.c
gcc demo.c -o demo
clean:
rm demo
上面是一個makefile的例子,我們簡要說明一下makefile的書寫規則:
編譯目標:依賴檔案
編譯命令
然後我們使用make
命令去解釋執行makefile
,我們可以使用make 編譯目標
去執行特定的語句,比如在上面的例子當中我們執行make demo
的話就會執行gcc demo.c -o demo
命令。
其實上面我們也可以直接使用make
命令,不需要指定編譯目標,因為make
會自己尋找第一個目標作為被執行的目標。
在上面的程式碼當中我們當我們執行make
的時候尋找到makefile
檔案的第一個目標demo
,但是因為我沒有改動demo.c
這個檔案,而且這個檔案已經編譯過了,因此我們沒有必要再去編譯這個檔案,這也是make
給我們提供的一個非常好的特性,我們不需要重新編譯已經編譯好的檔案,這在一個大型專案當中是非常有用的,當我們的專案當中有成千上萬的檔案的時候,如果我們重新編譯每一個檔案的話,那麼編譯的時間消耗是非常大的。因此我們在執行make
執行執行clean
編譯目標先刪除demo
這個編譯結果,然後在執行make
這次它再找到demo
目標,而此時demo
已經被刪除了,因此會重新編譯。
當我們在命令列當中輸入make
的時候他的執行流程如下:
demo
這個編譯目標,而不是clean
這個目標,因為clean
是第二個編譯目標。make編譯的過程為尋找編譯目標,依賴關係,如果依賴的檔案還存在依賴那麼make會一層一層的尋找下去,只要所有的依賴都被成功解析了,才會最終執行第一個編譯目標的編譯命令。但是在makefile當中不被第一個編譯目標的目標的編譯命令是不會被執行的,比如上面我們執行make的時候會執行demo
編譯目標,但是不會執行clean
編譯目標。
下面我們寫一個例子瞭解make解析依賴的過程:
main: demo.o myprint.o
gcc demo.o myprint.o -o out
echo make 解析編譯完成
demo.o: demo.c
gcc -c demo.c -o demo.o
myprint.o: myprint.c
gcc -c myprint.c -o myprint.o
clean:
rm myprint.o demo.o out
執行make之後的結果如下圖所示:
因為我們沒有指定編譯的目標,因此make會尋找一個編譯目標,也就是main
,但是在main
當中依賴兩個檔案demo.o
和myprint.o
,因此make會再去執行demo.o
和myprint.o
兩個編譯目標,當這兩個編譯目標被完成之後才能執行main
的編譯,根據make的輸出結果我們可以得到這一個結果的印證。
在makefile當中我們可以定義一些我們的變數這樣就可以避免反覆輸入了。比如我們經常會變編譯檔案的時候增加一些編譯選項,比如像下面這樣:
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o out
demo.o: demo.c
gcc $(cflags) demo.c -o demo.o
myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o
clean:
rm myprint.o demo.o out
在上面的makefile當中我們定義了一個變數cflags
並且在編譯命令當中使用,我們定義變數的方法其實和shell差不多,我們直接使用=
可以定義變數,然後使用$(變數名)
可以使用變數,因為上面的例子當中cflag=-c
比較短,比較簡單,但是如果當我們的編譯引數很多很長的時候使用變數就非常有效了,而且如果在一個專案當中如果有成千上萬個檔案我們像統一改變編譯時候的引數的話,我們一個一個改是很麻煩的,但是如果我們使用變數就可以做到一改全改。
在makefile當中我們也可以使用include命令去包含其他的makefile檔案,比如我們將上面的makefile檔案分成兩個部分makefile
和submakefile
:
makefile:
include submakefile
demo.o: demo.c
gcc $(cflags) demo.c -o demo.o
myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o
clean:
rm myprint.o demo.o out
submakefile:
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o out
然後在目錄下執行make命令, 得到的效果和前文當中提到的makefile結果是一樣的,這就相當於將submakefile的內容放到include語句的位置。
在上面談到的makefile當中有一個clean
的編譯目標用於清除我們編譯的結果檔案,現在我們在當前的目錄下面增加一個檔案clean
在執行make命令,我們的makefile檔案如下:
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main
demo.o: demo.c
gcc $(cflags) demo.c -o demo.o
myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o
clean:
rm myprint.o demo.o main
然後執行下面的命令(touch命令是新增一個檔案,touch clean 就是往目錄中增加一個名字為clean的檔案):
我們可以看到當目錄下面增加一個clean檔案之後,我們使用make clean
命令的時候,make給我們的提示資訊為clean檔案是最新的了。這是因為當執行make 編譯目標
,make首先會檢查當前目錄下面是否存在clean檔案如果不存在則執行編譯目標clean的命令。如果存在clean檔案的話,make會檢查編譯目標clean的依賴檔案是否發生更改,如果發生更改了那麼就會執行clean對應的命令,但是在上面的makefile當中clean沒有依賴檔案,因此相當於他的依賴檔案沒有發生改變,make不會執行編譯目標clean對應的命令了。
但是我們的需求是仍然希望執行clean之後的命令,這個時候我們就可以使用PHONY了,他可以保證即使存在clean檔案的時候,make命令依然會執行編譯目標clean對應的命令。
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main
demo.o: demo.c
gcc $(cflags) demo.c -o demo.o
myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o
clean:
rm myprint.o demo.o main
.PHONY: clean # 增加這一行
執行結果如下圖所示:
我們現在來測試一下當我們命令生成的檔案和編譯目標不同名的時候,make是如何解釋執行makefile的,makefile的內容如下:
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o out # 這裡的輸出檔案是 out 而不是 main 輸出檔案名字和目標不同名
demo.o: demo.c
gcc $(cflags) demo.c -o demo.o
myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o
clean:
rm myprint.o demo.o main
.PHONY: clean
執行結果:
從上面的結果我們可以發現,當我們編譯的結果檔案(out)和編譯目標(main)不同名的時候make每次都回去執行這個目標,因為make在進行檢測的時候沒有發現main這個檔案,因此每次執行make命令的時候都會去執行這個編譯目標。
我們現在修改前面的makefile,修改的結果如下(當前目錄下面有兩個檔案demo.c和myprint.c):
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main
%.o: %.c
gcc $(cflags) $<
clean:
rm myprint.o demo.o main
.PHONY: clean
上面的makefile當中有一個萬用字元%,其中%.c表示當前目錄下面的所有的以.c結尾的檔案。上面的makefile與下面的makefile的效果是一樣的:
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main
demo.o:demo.c
gcc $(cflags) demo.c
myprint.o:myprint.c
gcc $(cflags) myprint.c
clean:
rm myprint.o demo.o main
.PHONY: clean
在上面的makefile當中 $< 表示第一個依賴檔案,上面的%就是一個萬用字元,%.c可以匹配任何以.c結尾的檔案,你可能會有疑問%.c匹配不是所有的.c檔案嗎?那麼等價的結果不應該是:
cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main
demo.o myprint.o:demo.c myprint.c
gcc $(cflags) demo.c
clean:
rm myprint.o demo.o main
.PHONY: clean
事實上你可以認為make會將萬用字元匹配的檔案一一展開,有幾個檔案就將產生對應數目的編譯目標,而不是將他們都放在一起。
在一個工程專案當中我們可以會有許多的目錄以及原始檔,而make命令只會自動搜尋當前目錄下的檔案,如果當前目錄下沒有那麼make命令就會產生錯誤。因此make也給我們提供了一種檔案搜尋的功能。
在makefile當中,我們可以使用VAPTH
指定make去搜尋檔案的路徑。我們先來測試一下當我們沒有使用VPATH
的時候指定其他目錄下的檔案會出現什麼情況,我們的檔案目錄結構如下圖所示:
我們的makefile內容如下(先把VPATH的那一行註釋掉):
cflags=-c
# VPATH=./files
main: demo.o myprint.o a.o b.o
gcc demo.o myprint.o a.o b.o -o main
demo.o:demo.c
gcc $(cflags) demo.c
myprint.o:myprint.c
gcc $(cflags) myprint.c
a.o: a.c
gcc $(cflags) a.c
b.o: b.c
gcc $(cflags) b.c
clean:
rm myprint.o demo.o main
.PHONY: clean
執行結果如下圖所示:
我們現在修改我們的makefile檔案如下:
cflags=-c
VPATH=./files
main: demo.o myprint.o a.o b.o
gcc demo.o myprint.o a.o b.o -o main
demo.o:demo.c
gcc $(cflags) demo.c
myprint.o:myprint.c
gcc $(cflags) myprint.c
a.o: a.c
gcc $(cflags) $<
b.o: b.c
gcc $(cflags) $<
clean:
rm myprint.o demo.o main
.PHONY: clean
再次執行make命令,執行結果如下圖所示:
我們可以看到仍然出錯了,這是因為雖然make可以找到a.c和b.c的位置,但是在執行編譯命令的時候我們執行的依然是gcc -c a.c
,這條命令是會在當前目錄下去尋找a.c
的,而不會在其他路徑下去尋找a.c
,在這裡我們可以使用$<
去解決這個問題,$<
表示第一個依賴的檔案。
修改之後的makefile檔案如下所示:
cflags=-c
VPATH=./files
main: demo.o myprint.o a.o b.o
gcc demo.o myprint.o a.o b.o -o main
demo.o:demo.c
gcc $(cflags) demo.c
myprint.o:myprint.c
gcc $(cflags) myprint.c
a.o: a.c
gcc $(cflags) $<
b.o: b.c
gcc $(cflags) $<
clean:
rm myprint.o demo.o main a.o b.o
.PHONY: clean
執行結果如下圖所示:
如果你想設定多條目錄的話,設定的規則為VPATH=dir1:dir2:dir3:dir4
。
在本篇文章當中主要介紹了一些makefile的基礎用法,和一些常用的小技巧,雖然不是很難,但是掌握了可以很大的跳高我們的工作效率,也可以方便我們閱讀別人寫的makefile檔案。
以上就是本篇文章的所有內容了,我是LeHung,我們下期再見!!!更多精彩內容合集可存取專案:https://github.com/Chang-LeHung/CSCore
關注公眾號:一無是處的研究僧,瞭解更多計算機(Java、Python、計算機系統基礎、演演算法與資料結構)知識。