今天我們來玩玩Makefile。
這邊是借鑒的陳皓老師的《跟我一起寫 Makefile》
pdf下載鏈接如下。
鏈接:https://pan.baidu.com/s/1woRq2nEkgzLv1o5uE0FZHg?pwd=mhrh?
提取碼:mhrh
我們之前已經算是入門了gcc,那我們的下一站就是Makefile,上一篇我們也發現了,當我們的main.c包含了自己寫的頭文件,那么編譯命令會變得很長,如果包含的頭文件很多,那么光是敲編譯命令都夠花我們半條命了。
所以也有下面這個說法。
會不會寫 makefile ,從一個側面說明了一個人是否具備完成大型工程的能力。
那么Makefile到底是做什么的呢?簡單來說,Makefile通過定義一系列編譯規則,指導make工具如何根據源文件的修改情況來執行增量編譯,從而極大地提高了開發效率,Makefile寫出來也就是一個腳本文件。
使用Makefile不僅使得編譯過程自動化,還帶來了諸多好處:
- 自動化處理復雜的依賴關系:Makefile能夠自動處理和解析項目中各文件之間的復雜依賴關系,確保只有必要的部分被重新編譯。
- 支持跨平臺構建:通過編寫與平臺無關的Makefile,可以在不同操作系統間實現一致的構建過程。
- 提高開發效率:開發者只需關注源代碼的編寫,編譯過程的大部分操作都由Makefile自動完成,減少了手動操作的錯誤和時間消耗。
那么廢話不多說,我們直接開始先寫一個Makefile吧。
先別管這里的內容是什么意思,這內容我們寫到Makefile里,Makefile就是這個腳本文件的名字,沒有后綴,當然了也可以叫makefile和GNUmakefile(最好不要叫這個,因為只有GNU make才能識別)。
我們寫好之后要執行Makefile,就直接執行一個make命令。
然后就可以看到執行了一堆命令,這些命令是我們寫在Makefile里面的。
并且生成了下面一堆中間文件。
那其實大部分情況我們是不需要中間文件的,那么我們是不是也可以把刪除中間文件的命令寫進Makefile里呢?那當然是可以了,因為Makefile本質上就是腳本文件,自然是可以寫的。
然后我們執行
make clean
可以看到執行了我們寫在Makefile里面的最后的那個語句。
接下來我們開始揭秘Makefile。
我們先看第一行test:test.o,這實際上是一個規則。這段的含義是我們需要生成test(也就是目標文件),為了生成test首先需要擁有test.o(也就是依賴文件),通過執行下面首行縮進一個tab的那個命令來生成。
如果當前目錄下沒有依賴文件,那么就會在Makefile里找有沒有目標文件是依賴文件的規則。
那么前四個規則大家應該就能看懂了。
但是第五個clean它不是簡單的規則,它是一個特殊的目標,一般用來刪除編譯過程中產生的中間文件,并且我們看到它是沒有依賴的。
執行的時候就是輸入命令 make clean。
因為clean涉及到刪除,而在Linux中刪除就是真刪除了,不像Window里還能從回收站里找到,因此編寫clean的時候我們需要格外小心,但是保不齊我們會失誤,那怎么辦呢?
我們可以在make clean后面加個-n選項來模擬刪除,然后再次檢查是否出錯,此時還不是真的刪除,因此還有改正的機會。
如上可以看得出來當我們執行make clean命令加上-n選項之后,它會展示出執行的刪除命令,但是并沒有真正執行,這時我們就可以對make clean 進行檢查了。
除了直接在Makefile里寫clean,更穩健的寫法是下面這樣的。
?其中.PHONY表示后面的clean是一個偽目標,并不是正常的目標。并且在 rm 前面加上了一個 - ,這表示當我們刪除某些不存在的文件的時候忽略報錯,接著往后執行。
當然還有個潛規則就是clean放在Makefile的最后面。
接著再回到我們的第一版最簡單的Makefile,它是由多個規則構成的,每個規則里有目標文件,依賴文件和執行命令。在Makefile里,它默認會把第一個規則中的目標文件當成是最終目標,也就是說它不一點會執行每個規則,而是只要能夠生成最終目標就行。
要指定最終目標也是可以的,在Makefile開頭加上ALL:最終目標即可。像下面這樣,實際上不一定非得叫ALL,原理就是Makefile會以第一個規則為最終目標,那么我放在第一行的那就是最終目標。
上面就是Makefile最基礎最基礎的內容了。
那接下來我們就可以更進一步了。
既然Makefile是腳本文件,那么它有變量也是正常的對叭。
一般寫在文件開頭,格式就是? 變量名 = 變量值 ,當我們使用時是這樣的 $(?變量名 ),這邊需要提的是Makefile中的變量比較接近于宏定義,也就是直接替換的,沒有類型。
光講可能比較抽象,我們直接看下面的例子。
上面例子把目標文件直接賦值給了變量targetFile,我們在規則中可以使用$(targetFile)的方式取出test的這個值去使用,當然了上面只是簡單地做個示范,我們還可以拿變量去裝一些更復雜的東西,比如說一堆的文件(沒錯,一個變量能裝的可不止一個文件,用空格隔開可以裝多個)
那我們要給變量賦值賦上一堆文件,除了一個個手敲上去,還有別的更省力的方式那就是通配符。
比如說我們需要給一個變量賦值當前目錄下所有的 .c 文件,那么我們用通配符就可以非常方便的賦值。
大家想的是下面這樣的對叭。
cfile = *.c
但是實際上這樣是不行的,這樣在Makefile中,cfile這個變量的值就是 *.c ,而不是我們想要的所有 .c 文件,這時候就需要用上函數了,既然Makefile是腳本文件,那么有個函數也是很正常的對叭。
正確的寫法是下面這樣的。
cfile = $(wildcard *.c)
這個函數就是 $(wildcard? ),在括號里,wildcard后寫的通配符就會被展開,就會是我們想要的效果了。
除了上面我們自己去寫變量,Makefile里有一些自動化變量我們可以直接去使用的,我們挑幾個常用的來說。
$@ : 本條規則中的目標
$< : 本條規則中第一個依賴,如果第一個依賴代表多個文件(后面會說怎么實現),那么$<就表示為將這多個文件一個個取出來。
$^ :本條規則中的所有依賴,用空格分隔。
$* : 可以簡單理解成目標文件的名字但是不含后綴名。
$??:比目標更新的依賴,也就是最后修改時間比目標更晚的依賴。
用了上面的自動化變量,那么我們第一版的Makefile可以寫成下面這樣了。
現在我們改一改我們的test.c,像下面這樣。包含了自己寫的頭文件,用了里面的方法。
對應的在Makefile里也需要修改。我們添加了對于mytool1的編譯。
是可以正常make的。
然后我們發現,后面兩個規則中的命令是一樣的,那么實際上我們是可以對他們進行一個合并操作的,也就是說在一個規則里,可以有多個目標。可以像下面這樣改寫,但是由于gcc -o選項只能輸出一個文件,因此在命令里不能使用 $@,但是還好-c選項可以不指定輸出文件,默認就是輸入文件的后綴改成 .o當輸出文件了。
也是可以正常make的。
這個例子主要是想說在Makefile中,在一個規則里可以有多個目標。然后同一個目標也可以有多個規則(這邊不演示了)。
然后實際上我們還有種更簡單的寫法,那就是模式規則。
這邊直接貼出怎么寫的。
看不懂什么意思沒關系,我們make一下,make之后會顯示出它執行的命令,然后我們再分析分析。
看的出來Makefile直接幫我們把我們需要的依賴都執行了。
這是因為上面的%可以看成是任意的東西(類似于通配符中的*),當我們的目標的依賴不存在的時候,Makefile就會在規則中尋找是否目標的依賴和哪個規則的目標一致,找到就會執行這個規則中的命令。就比如說我們上面的Makefile,需要尋找目標為test.o以及目標為mytool1.o的依賴,最終都找到了 %.o:%.c 這個規則,因為%代表任意東西,那么自然是可以匹配上的,這就是模式規則強大的地方,我們可以少寫很多東西了。
不過上面這種寫法有一個可以改進的點,那就是將$^改成$<,我們來看看二者的區別。
我們上面的例子簡單,所以沒什么差別,但是當我們的依賴變多之后,依賴嵌套依賴之后,我們就得用$<了。
使用模式規則也有個弊端,那就是模式規則會匹配所有符合條件的,因為%可以匹配任意東西,因此只要后綴符合,都會被我們的模式規則匹配到,如果我只有一部分的文件需要用模式規則中的命令怎么辦呢?
這就要用到我們的靜態模式規則了。
在模式規則的基礎上,在前面加上需要匹配到的目標即可。
可能有小伙伴會說,這樣不就又繞回去了嘛,還是要把目標的名字都寫出來。那其實我們可以再靜態模式規則的后面再寫個模式規則,而只在靜態模式規則中寫上需要特殊處理的目標即可,剩下的可以都丟給后面的模式規則去兜底。
一般情況下我們不會將工具文件和我們的主文件放在同一級目錄下,因此我新建個文件夾my_tools,然后將工具文件放進去,接著我們就毫不意外地發現Makefile找不到庫文件了。
我們可以在Makefile中指定頭文件目錄,指定的方式也很簡單,就像下面這樣。
VPATH = 路徑1:路徑2……
可以指定多個路徑,用冒號:隔開即可。
指定完之后仍然報錯,但這就是鏈接階段報的錯誤了。
我們需要在規則的命令中添加頭文件路徑。
最終是下面這樣的。
可以正常運行,在Makefile執行規則命令的時候也幫我們把頭文件路徑加上去了。
VPATH是一個特殊變量,指明了這個特殊變量,當Makefile找不到依賴文件,也沒有目標是依賴的規則,那么Makefile就會到VPATH指明的路徑去尋找。
除了VPATH還有vpath,也就是小寫的,但是vpath不是變量,而是關鍵字,使用可以參考下面這樣。
看的出來vpath一次只能添加一條路徑,但是和VPATH相比的話,vpath可以更細致地指定路徑,可以給不同后綴的文件指定不同的路徑。
至此我們就算是入門Makefile成功了,相信各位小伙伴已經能夠給自己以前寫的小項目編寫(相對)高效的Makefile了。
如果想進一步了解Makefile的話,最好的方法是去看官方文檔,當然了,也可以看看陳皓老師寫的《跟我一起寫 Makefile》,下載鏈接在本文的開頭。