徹底掌握Makefile(一)
徹底掌握Makefile(一)
介紹
makefile就是一個可以被make命令解析的文件,他定義了一系列編譯的規則,幫助我們更加方便、簡潔的去完成編譯的過程。在一個大工程當中我們會有各種各樣的文件,我們可能會分模組去存放各種文件,可能有些文件還依賴其他的文件,因此我們在編譯的時候需要先將被依賴的文件先編譯,其他文件後編譯,而我們使用makefile就可以更好的去完成這件事兒。
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命令的工作流程
當我們在命令行當中輸入make
的時候他的執行流程如下:
- make命令首先會在當前目錄下面尋找makefile或者Makefile文件。
- 尋找到makefile文件之後,他會在文件當中尋找到一個編譯目標,比如在上面的makefile文件當中他會找到
demo
這個編譯目標,而不是clean
這個目標,因為clean
是第二個編譯目標。 - 然後make會解析編譯目標的依賴,如果這個依賴是其他的編譯目標A的話,那麼make會先完成它依賴的編譯目標A的命令,如果它依賴的編譯目標A也存在依賴B的話,make就會去執行依賴的B的編譯命令,如此的遞歸下去,知道有所得依賴目標都存在了,才會完成第一個編譯目標的編譯,這個也很好理解,只有依賴文件都存在了我們才能夠完成正確的編譯過程。
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的輸出結果我們可以得到這一個結果的印證。
Makefilex小技巧
Makefile當中的變數
在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當中我們也可以使用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中的PHONY
在上面談到的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的通配符
我們現在修改前面的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會將通配符匹配的文件一一展開,有幾個文件就將產生對應數目的編譯目標,而不是將他們都放在一起。
Makefile文件自動搜索
在一個工程項目當中我們可以會有許多的目錄以及源文件,而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,我們下期再見!!!更多精彩內容合集可訪問項目://github.com/Chang-LeHung/CSCore
關注公眾號:一無是處的研究僧,了解更多電腦(Java、Python、電腦系統基礎、演算法與數據結構)知識。