Makefile 文件描述了 Linux 系統下 C/C++ 工程的編譯規則,它用來自動化編譯 C/C++ 項目。一旦寫編寫好 Makefile 文件,只需要一個 make 命令,整個工程就開始自動編譯,不再需要手動執行 GCC 命令。
一個中大型 C/C++ 工程的源文件有成百上千個,它們按照功能、模塊、類型分別放在不同的目錄中,Makefile 文件定義了一系列規則,指明了源文件的編譯順序、依賴關系、是否需要重新編譯等。
參考:Makefile教程
1. Makefile文件是什么?
Windows環境:如果你是在 Windows 下作開發的話不需要去考慮這個問題,因為 Windows 下的集成開發環境(IDE)已經內置了 Makefile,或者說會自動生成 Makefile,我們不用去手動編寫。
Linux環境:Linux 中卻不能這樣,需要我們去手動的完成這項工作。不懂 Makefile,就操作不了多文件編程,就完成不了相對於大的工程項目的操作。
Makefile 可以簡單的認為是一個工程文件的編譯規則,描述了整個工程的編譯和鏈接等規則。其中包含了那些文件需要編譯,那些文件不需要編譯,那些文件需要先編譯,那些文件需要后編譯,那些文件需要重建等等。編譯整個工程需要涉及到的,在 Makefile 中都可以進行描述。換句話說,Makefile 可以使得我們的項目工程的編譯變得自動化,不需要每次都手動輸入一堆源文件和參數。
以 Linux 下的C語言開發為例來具體說明一下,多文件編譯生成一個文件,編譯的命令如下所示:
gcc -o outfile name1.c name2.c ...
outfile 要生成的可執行程序的名字,nameN.c 是源文件的名字。
這是我們在 Linux 下使用 gcc 編譯器編譯 C 文件的例子。如果我們遇到的源文件的數量不是很多的話,可以選擇這樣的編譯方式。如果源文件非常的多的話,就會遇到下面的這些問題:
1) 編譯的時候需要鏈接庫的的問題。
2) 編譯大的工程會花費很長的時間。
對於這樣的問題我們 Makefile 可以解決,Makefile 支持多線程並發操作,會極大的縮短我們的編譯時間,並且當我們修改了源文件之后,編譯整個工程的時候,make 命令只會編譯我們修改過的文件,沒有修改的文件不用重新編譯,也極大的解決了我們耗費時間的問題。
2. Makefile文件中包含哪些規則?
Makefile 描述的是文件編譯的相關規則,它的規則主要是兩個部分組成,分別是依賴的關系和執行的命令,其結構如下所示:
targets : prerequisites
command
相關說明如下:
- targets:規則的目標,可以是 Object File(一般稱它為中間文件),也可以是可執行文件,還可以是一個標簽;
- prerequisites:是我們的依賴文件,要生成 targets 需要的文件或者是目標。可以是多個,也可以是沒有;
- command:make 需要執行的命令(任意的 shell 命令)。可以有多條命令,每一條命令占一行。Makefile 中的任何命令都要以
tab
鍵開始。
舉例:
test:test.c gcc -o test test.c
上述代碼實現的功能就是編譯 test.c 文件,通過這個實例可以詳細的說明 Makefile 的具體的使用。
其中 test 是的目標文件,也是我們的最終生成的可執行文件。依賴文件就是 test.c 源文件,重建目標文件需要執行的操作是gcc -o test test.c
。這就是 Makefile 的基本的語法規則的使用。
簡單的概括一下Makefile 中的內容,它主要包含有五個部分,分別是:
1) 顯式規則
顯式規則說明了,如何生成一個或多的的目標文件。這是由 Makefile 的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
2) 隱晦規則
由於我們的 make 命名有自動推導的功能,所以隱晦的規則可以讓我們比較粗糙地簡略地書寫 Makefile,這是由 make 命令所支持的。
3) 變量的定義
在 Makefile 中我們要定義一系列的變量,變量一般都是字符串,這個有點像C語言中的宏,當 Makefile 被執行時,其中的變量都會被擴展到相應的引用位置上。
4) 文件指示
其包括了三個部分,一個是在一個 Makefile 中引用另一個 Makefile,就像C語言中的 include 一樣;另一個是指根據某些情況指定 Makefile 中的有效部分,就像C語言中的預編譯 #if 一樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在后續的部分中講述。
5) 注釋
Makefile 中只有行注釋,和 UNIX 的 Shell 腳本一樣,其注釋是用“#”字符,這個就像 C/C++ 中的“//”一樣。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框進行轉義,如:“\#”。
3. Makefile的工作流程
當我們在執行 make 這條命令的時候,make 就會去當前文件下找要執行的編譯規則,也就是 Makefile 文件。我們編寫 Makefile 的時候可以使用的文件的名稱 :"GNUmakefile" 、"makefile" 、"Makefile" ,make 執行時會去尋找 Makefile 文件,找文件的順序也是這樣的。
工作流程
清除工作目錄中的過程文件
我們在使用的時候會產生中間文件會讓整個文件看起來很亂,所以在編寫 Makefile 文件的時候會在末尾加上這樣的規則語句:
.PHONY:clean clean: rm -rf *.o test
其中 "*.o" 是執行過程中產生的中間文件,"test" 是最終生成的執行文件。我們可以看到 clean 是獨立的,它只是一個偽目標(在《Makefile偽目標》的章節中詳細介紹),不是具體的文件。不會與第一個目標文件相關聯,所以我們在執行 make 的時候也不會執行下面的命令。在shell 中執行 "make clean" 命令,編譯時的中間文件和生成的最終目標文件都會被清除,方便我們下次的使用。
4. Makefile通配符的使用
Makefile 是可以使用 shell 命令的,所以 shell 支持的通配符在 Makefile 中也是同樣適用的。 shell 中使用的通配符有:"*","?","[...]"。具體看一下這些通配符的表示含義和具體的使用方法。
5. Makefile變量的定義和使用
Makefile 文件中定義變量的基本語法如下:
變量的名稱=值列表
它並沒有像其它語言那樣定義變量的時候需要使用數據類型。變量的名稱可以由大小寫字母、阿拉伯數字和下划線構成。等號左右的空白符沒有明確的要求,因為在執行 make 的時候多余的空白符會被自動的刪除。至於值列表,既可以是零項,又可以是一項或者是多項。如: VALUE_LIST = one two three
調用變量的時候可以用 "$(VALUE_LIST)" 或者是 "${VALUE_LIST}" 來替換,這就是變量的引用。
舉例:
OBJ=main.o test.o test1.o test2.o test:$(OBJ) gcc -o test $(OBJ)
變量的基本賦值
知道了如何定義,下面我們來說一下 Makefile 的變量的四種基本賦值方式:
- 簡單賦值 ( := ) 編程語言中常規理解的賦值方式,只對當前語句的變量有效。
- 遞歸賦值 ( = ) 賦值語句可能影響多個變量,所有目標變量相關的其他變量都受影響。
- 條件賦值 ( ?= ) 如果變量未定義,則使用符號中的值定義變量。如果該變量已經賦值,則該賦值語句無效。
- 追加賦值 ( += ) 原變量用空格隔開的方式追加一個新值。
6. Makefile自動化變量
自動化變量可以理解為由 Makefile 自動產生的變量。
在模式規則中,規則的目標和依賴的文件名代表了一類的文件。規則的命令是對所有這一類文件的描述。我們在 Makefile 中描述規則時,依賴文件和目標文件是變動的,顯然在命令中不能出現具體的文件名稱,否則模式規則將失去意義。
那么模式規則命令中該如何表示文件呢?就需要使用“自動化變量”,自動化變量的取值根據執行的規則來決定,取決於執行規則的目標文件和依賴文件。下面是對所有的自動化變量進行的說明:
我們在執行 make 的時候,make 會自動識別命令中的自動化變量,並自動實現自動化變量中的值的替換,這個類似於編譯C語言文件的時候的預處理的作用。
7. Makefile目標文件搜索(VPATH和vpath)
如果所有的源文件都是存放在與 Makefile 相同的目錄下,只要依賴的文件存在,並且依賴規則沒有問題,執行 make命令整個工程就會按照對我們編寫規則去編譯,最終會重建目標文件。
如果需要的文件是存在於不同的路徑下,在編譯的時候要去怎么辦呢(不改變工程的結構)?這就用到了 Makefile 中為我們提供的目錄搜索文件的功能。
常見的搜索的方法的主要有兩種:一般搜索VPATH
和選擇搜索vpath
。乍一看只是大小寫的區別,其實兩者在本質上也是不同的。
VPATH 和 vpath 的區別:
- VPATH 是變量,更具體的說是環境變量,Makefile 中的一種特殊變量,使用時需要指定文件的路徑;
- vpath 是關鍵字,按照模式搜索,也可以說成是選擇搜索。搜索的時候不僅需要加上文件的路徑,還需要加上相應限制的條件。
VPATH的使用
在 Makefile 中可以這樣寫:
VPATH := src
我們可以這樣理解,把 src 的值賦值給變量 VPATH,所以在執行 make 的時候會從 src 目錄下找我們需要的文件。
當存在多個路徑的時候我們可以這樣寫:VPATH := src car
或者是 VPATH := src:car
多個路徑之間要使用空格或者是冒號隔開,表示在多個路徑下搜索文件。搜索的順序為我們書寫時的順序,拿上面的例子來說,我們應該先搜索 src 目錄下的文件,再搜索 car 目錄下的文件。
vpath的使用
VPATH 是搜索路徑下所有的文件,而 vpath 更像是添加了限制條件,會過濾出一部分再去尋找。
具體用法:
1) vpath PATTERN DIRECTORIES
2) vpath PATTERN
3) vpath
( PATTERN:可以理解為要尋找的條件,DIRECTORIES:尋找的路徑 )
首先是用法一,命令格式如下:
vpath test.c src
可以這樣理解,在 src 路徑下搜索文件 test.c。
使用什么樣的搜索方法,主要是基於編譯器的執行效率。
使用 VPATH 的情況是路徑下的文件較少,或者是搜索的文件不能使用通配符表示,這些情況下使用VPATH最好。
如果存在某個路徑的文件特別的多或者是可以使用通配符表示的時候,就不建議使用 VPATH 這種方法,為什么呢?因為 VPATH 在去搜索文件的時沒有限制條件,所以它會去檢索這個目錄下的所有文件,每一個文件都會進行對比,搜索和我們目錄名相同的文件,不僅速度會很慢,而且效率會很低。我們在這種情況下就可以使用 vpath 搜索,它包含搜索條件的限制,搜索的時候只會從我們規定的條件中搜索目標,過濾掉不符合條件的文件,當然查找的時候也會比較的快。
8. Makefile路徑搜索使用案例
9. Makefile隱含規則
所謂的隱含規則就是需要我們做出具體的操作,系統自動完成。編寫 Makefile 的時候,可以使用隱含規則來簡化Makefile 文件編寫。
test:test.o gcc -o test test.o test.o:test.c
我們可以在 Makefile 中這樣寫來編譯 test.c 源文件,相比較之前少寫了重建 test.o 的命令。但是執行 make,發現依然重建了 test 和 test.o 文件,運行結果卻沒有改變。這其實就是隱含規則的作用。
因為完整的文件是:
test:test.o gcc -o test test.o test.o:test.c
gcc -o test.o test.c
在某些時候其實不需要給出重建目標文件的命令,有的甚至可以不需要給出規則。實例:
test:test.o gcc -o test test.o
運行的結果是相同的。
注意:隱含條件只能省略中間目標文件重建的命令和規則,但是最終目標的命令和規則不能省略。
隱含規則的具體的工作流程:
make 執行過程中找到的隱含規則,提供了此目標的基本依賴關系。確定目標的依賴文件和重建目標需要使用的命令行。隱含規則所提供的依賴文件只是一個基本的(在C語言中,通常他們之間的對應關系是:test.o 對應的是 test.c 文件)。當需要增加這個文件的依賴文件的時候要在 Makefile 中使用沒有命令行的規則給出。
10. Makefile ifeq、ifneq、ifdef和ifndef(條件判斷)
需要解決的問題:要根據判斷,分條件執行語句。
條件語句的作用:條件語句可以根據一個變量的值來控制 make 執行或者時忽略 Makefile 的特定部分,條件語句可以是兩個不同的變量或者是常量和變量之間的比較。
條件語句使用優點:Makefile 中使用條件控制可以做到處理的靈活性和高效性。
ifeq (ARG1, ARG2)
else
endif
11. Makefile偽目標
所謂的偽目標可以這樣來理解,它並不會創建目標文件,只是想去執行這個目標下面的命令。偽目標的存在可以幫助我們找到命令並執行。
使用偽目標有兩點原因:
- 避免我們的 Makefile 中定義的只執行的命令的目標和工作目錄下的實際文件出現名字沖突。
- 提高執行 make 時的效率,特別是對於一個大型的工程來說,提高編譯的效率也是我們所必需的。
我們先來看一下第一種情況的使用。如果需要書寫這樣一個規則,規則所定義的命令不是去創建文件,而是通過 make 命令明確指定它來執行一些特定的命令。實例:
clean: rm -rf *.o test
規則中 rm 命令不是創建文件 clean 的命令,而是執行刪除任務,刪除當前目錄下的所有的 .o 結尾和文件名為 test 的文件。當工作目錄下不存在以 clean 命令的文件時,在 shell 中輸入 make clean 命令,命令 rm -rf *.o test 總會被執行 ,這也是我們期望的結果。
如果當前目錄下存在文件名為 clean 的文件時情況就會不一樣了,當我們在 shell 中執行命令 make clean,由於這個規則沒有依賴文件,所以目標被認為是最新的而不去執行規則所定義的命令。因此命令 rm 將不會被執行。
為了解決這個問題,刪除 clean 文件或者是在 Makefile 中將目標 clean 聲明為偽目標。
將一個目標聲明稱偽目標的方法是將它作為特殊的目標.PHONY
的依賴,如下:
.PHONY:clean
這樣 clean 就被聲明成一個偽目標,無論當前目錄下是否存在 clean 這個文件,當我們執行 make clean 后 rm 都會被執行。而且當一個目標被聲明為偽目標之后,make 在執行此規則時不會去試圖去查找隱含的關系去創建它。這樣同樣提高了 make 的執行效率,同時也不用擔心目標和文件名重名而使我們的編譯失敗。
在書寫偽目標的時候,需要聲明目標是一個偽目標,之后才是偽目標的規則定義。目標 "clean" 的完整書寫格式如下:
.PHONY:clean clean: rm -rf *.o test
12. Makefile常用字符串處理函數
函數的調用和變量的調用很像。引用變量的格式為$(變量名)
,函數調用的格式如下:
$(<function> <arguments>) 或者是 ${<function> <arguments>}
其中,function 是函數名,arguments 是函數的參數,參數之間要用逗號分隔開。而參數和函數名之間使用空格分開。調用函數的時候要使用字符“$”,后面可以跟小括號也可以使用花括號。這個其實我們並不陌生,我們之前使用過許多的函數,比如說展開通配符的函數 wildcard,以及字符串替換的函數 patsubst ,Makefile 中函數並不是很多。
字符串處理函數,這些都是我們經常使用到的函數,下面是對函數詳細的介紹。
1. 模式字符串替換函數,函數使用格式如下:
$(patsubst <pattern>,<replacement>,<text>)
函數說明:函數功能是查找 text 中的單詞是否符合模式 pattern,如果匹配的話,則用 replacement 替換。返回值為替換后的新字符串。實例:
OBJ=$(patsubst %.c,%.o,1.c 2.c 3.c) all: @echo $(OBJ)
執行 make 命令,我們可以得到的值是 "1.o 2.o 3.o",這些都是替換后的值。
注:all
通常也是一個.PHONY
目標【偽目標】
2. 字符串替換函數,函數使用格式如下:
$(subst <from>,<to>,<text>)
函數說明:函數的功能是把字符串中的 form 替換成 to,返回值為替換后的新字符串。
3. 去空格函數,函數使用格式如下:
$(strip <string>)
函數說明:函數的功能是去掉字符串的開頭和結尾的字符串,並且將其中的多個連續的空格合並成為一個空格。返回值為去掉空格后的字符串。
4. 查找字符串函數,函數使用格式如下:
$(findstring <find>,<in>)
函數說明:函數的功能是查找 in 中的 find ,如果我們查找的目標字符串存在。返回值為目標字符串,如果不存在就返回空。
5. 過濾函數,函數使用格式如下:
$(filter <pattern>,<text>)
函數說明:函數的功能是過濾出 text 中符合模式 pattern 的字符串,可以有多個 pattern 。返回值為過濾后的字符串。
6. 反過濾函數,函數使用格式如下:
$(filter-out <pattern>,<text>)
函數說明:函數的功能是功能和 filter 函數正好相反,但是用法相同。去除符合模式 pattern 的字符串,保留符合的字符串。返回值是保留的字符串。
7. 排序函數,函數使用格式如下:
$(sort <list>)
函數說明:函數的功能是將 <list>
中的單詞排序(升序)。返回值為排列后的字符串。注意:sort會去除重復的字符串。
8. 取單詞函數,函數使用格式如下:
$(word <n>,<text>)
函數說明:函數的功能是取出函數<text>
中的第n個單詞。返回值為我們取出的第 n 個單詞。
13. Makefile常用文件名操作函數
在編寫 Makefile 的時候,很多情況下需要對文件名進行操作。例如獲取文件的路徑,去除文件的路徑,取出文件前綴或后綴等等。
當遇到這樣的問題的時手動修改是不太可能的,因為文件可能會很多,而且 Makefile 中操作文件名可能不止一次。所以 Makefile 給我們提供了相應的函數去實現文件名的操作。
注意:下面的每個函數的參數字符串都會被當作或是一個系列【多個】的文件名來看待。
1. 取目錄函數,函數使用格式如下:
$(dir <names>)
函數說明:函數的功能是從文件名序列 names 中取出目錄部分。如果 names 中有 "/" ,取出的值為 最后一個反斜杠之前的部分。如果沒有反斜杠將返回“./”。實例:
OBJ=$(dir src/foo.c hacks) all: @echo $(OBJ)
執行 make 命令,我們可以得到的值是“src/ ./”。提取文件 foo.c 的路徑是 "/src" 和文件 hacks 的路徑 "./"。
2. 取文件函數,函數使用格式如下:
$(notdir <names>)
函數說明:函數的功能是從文件名序列 names 中取出非目錄的部分【文件名】。非目錄的部分是最后一個反斜杠之后的部分。返回值為文件非目錄的部分。
3. 取后綴名函數,函數使用格式如下:
$(suffix <names>)
函數說明:函數的功能是從文件名序列中 names 中取出各個文件的后綴名。返回值為文件名序列 names 中的后綴序列,如果文件沒有后綴名,則返回空字符串。
4. 取前綴函數,函數使用格式如下:
$(basename <names>)
函數說明:函數的功能是從文件名序列 names 中取出各個文件名的前綴部分【除了后綴的其他所有字符】。返回值為被取出來的文件的前綴名,如果文件沒有前綴名則返回空的字符串。
5. 添加后綴名函數,函數使用格式如下:
$(addsuffix <suffix>,<names>)
函數說明:函數的功能是把后綴 suffix 加到 names 中的每個單詞后面。返回值為添加上后綴的文件名序列。
6. 添加前綴名函數,函數使用格式如下:
$(addperfix <prefix>,<names>)
函數說明:函數的功能是把前綴 prefix 加到 names 中的每個單詞的前面。返回值為添加上前綴的文件名序列。 我們可以使用這個函數給我們的文件添加路徑。
7. 鏈接函數,函數使用格式如下:
$(join <list1>,<list2>)
函數說明:函數功能是把 list2 中的單詞對應的拼接到 list1 的后面。如果 list1 的單詞要比 list2的多,那么,list1 中多出來的單詞將保持原樣,如果 list1 中的單詞要比 list2 中的單詞少,那么 list2 中多出來的單詞將保持原樣。返回值為拼接好的字符串。
8. 獲取匹配模式文件名函數,命令使用格式如下:
$(wildcard PATTERN)
函數說明:函數的功能是列出當前目錄下所有符合模式的 PATTERN 格式的文件名。返回值為空格分隔並且存在當前目錄下的所有符合模式 PATTERN 的文件名。
14. Makefile中的其它常用函數
1、$(foreach <var>,<list>,<text>)
函數的功能是:把參數<list>
中的單詞逐一取出放到參數<var>
所指定的變量中,然后再執行<text>
所包含的表達式。
每一次<text>
會返回一個字符串,循環過程中,<text>所
返回的每個字符串會以空格分割,最后當整個循環結束的時候,<text>
所返回的每個字符串所組成的整個字符串(以空格分隔)將會是 foreach 函數的返回值。
所以<var>
最好是一個變量名,<list>
可以是一個表達式,而<text>
中一般會只用<var>
這個參數來一次枚舉<list>
中的單詞。
2、$(if <condition>,<then-part>)或(if<condition>,<then-part>,<else-part>)
3、$(call <expression>,<parm1>,<parm2>,<parm3>,...)
call 函數是唯一一個可以用來創建新的參數化的函數。我們可以用來寫一個非常復雜的表達式,這個表達式中,我們可以定義很多的參數,然后你可以用 call 函數來向這個表達式傳遞參數。
4、$(origin <variable>)
origin 函數不像其他的函數,它並不操作變量的值,它只是告訴你這個變量是哪里來的。
15. Makefile命令的編寫
命令回顯
通常 make 在執行命令行之前 會把要執行的命令輸出到標准輸出設備,我們稱之為 "回顯",就好像我們在 shell 環境下輸入命令執行時一樣。
OBJ=test main list all: echo $(OBJ)
執行make 或者make all,輸出:
如果規則的命令行以字符“@”開始,則 make 在執行的時候就不會顯示這個將要被執行的命令。典型的用法是在使用echo
命令輸出一些信息時。
OBJ=test main list all: @echo $(OBJ)
執行時將會得到test main list
這條輸出信息。
命令的執行
當規則中的目標需要被重建的時候,此規則所定義的命令將會被執行,如果是多行的命令,那么每一行命令將是在一個獨立的子 shell 進程中被執行。因此,多命令行之間的執行命令時是相互獨立的,相互之間不存在依賴。
在 Makefile 中書寫在同一行中的多個命令屬於一個完整的 shell 命令行,書寫在獨立行的一條命令是一個獨立的 shell 命令行。
因此:
在一個規則的命令中命令行 “cd”改變目錄不會對其后面的命令的執行產生影響。就是說之后的命令執行的工作目錄不會是之前使用“cd”進入的那個目錄。
如果達到這個目的,就不能把“cd”和其后面的命令放在兩行來書寫。而應該把這兩個命令放在一行上用分號隔開。這樣才是一個完整的 shell 命令行。
foo:bar/lose cd bar;gobble lose >../foo
如果想把一個完整的shell命令行書寫在多行上,需要使用反斜杠 (\)來對處於多行的命令進行連接,表示他們是一個完整的shell命令行。
16. Makefile include文件包含
包含其他文件使用的關鍵字是 "include",和 C 語言包含頭文件的方式相同。
當 make 讀取到 "include" 關鍵字的時候,會暫停讀取當前的 Makefile,而是去讀 "include" 包含的文件,讀取結束后再繼讀取當前的 Makefile 文件。"include" 使用的具體方式如下:
include <filenames>
filenames 是 shell 支持的文件名(可以使用通配符表示的文件)。
include 通常使用在以下的場合:
- 在一個工程文件中,每一個模塊都有一個獨立的 Makefile 來描述它的重建規則。它們需要定義一組通用的變量定義或者是模式規則。通用的做法是將這些共同使用的變量或者模式規則定義在一個文件中,需要的時候用 "include" 包含這個文件。
- 當根據源文件自動產生依賴文件時,我們可以將自動產生的依賴關系保存在另一個文件中。然后在 Makefile 中包含這個文件。
17. Makefile嵌套執行make
在一個大的工程文件中,不同的文件按照功能被划分到不同的模塊中,也就說很多的源文件被放置在了不同的目錄下。每個模塊可能都會有自己的編譯順序和規則,如果在一個 Makefile 文件中描述所有模塊的編譯規則,就會很亂,執行時也會不方便,所以就需要在不同的模塊中分別對它們的規則進行描述,也就是每一個模塊都編寫一個 Makefile 文件,這樣不僅方便管理,而且可以迅速發現模塊中的問題。這樣我們只需要控制其他模塊中的 Makefile 就可以實現總體的控制,這就是 make 的嵌套執行。
如何來使用呢?舉例說明如下:
subsystem: cd subdir && $(MAKE)
這個例子可以這樣來理解,在當前目錄下有一個目錄文件 subdir 和一個 Makefile 文件,子目錄 subdir 文件下還有一個 Makefile 文件,這個文件是用來描述這個子目錄文件的編譯規則。使用時只需要在最外層的目錄中執行 make 命令,當命令執行到上述的規則時,程序會進入到子目錄中執行 make。這就是嵌套執行 make,我們把最外層的 Makefile 稱為是總控 Makefile。
export的使用
使用 make 嵌套執行的時候,變量是否傳遞也是我們需要注意的。如果需要變量的傳遞,那么可以這樣來使用:
export <variable>
如果不需要那么可以這樣來寫:
unexport <variable>
<variable>是變量的名字,不需要使用 "$" 這個字符。如果所有的變量都需要傳遞,那么只需要使用 "export" 就可以,不需要添加變量的名字。
18. 嵌套執行make的案例
19. make命令參數和選項大匯總
20. Makefile目標類型大匯總
規則中的目標形式是多種多樣的,它可以是一個或多個的文件、可以是一個偽目標,這是我們之前講到過的,也是經常使用的。其實規則目標還可以是其他的類型,下面是對這些類型的詳細的說明。
強制目標
空目標文件
特殊的目標
多規則目標
21. Makefile變量的高級用法
高級使用方法有兩種:一種是變量的替換引用,一種是變量的嵌套引用。
1、變量的替換引用
我們定義變量的目的是為了簡化我們的書寫格式,代替我們在代碼中頻繁出現且冗雜的部分。它可以出現在我們規則的目標中,也可以是我們規則的依賴中。
我們使用的時候會經常的對它的值(表示的字符串)進行操作。遇到這樣的問題我們可能會想到我們的字符串操作函數,比如 "patsubst" 就是我們經常使用的。
但是我們使用變量同樣可以解決這樣的問題,我們通過下面的例子來具體的分析一下。
實例:
foo:=a.c b.c d.c obj:=$(foo:%.c=%.o) All: @echo $(obj)
這段代碼實現的功能是字符串的后綴名的替換,把變量 foo 中所有的以 .c 結尾的字符串全部替換成 .o 結尾的字符串。我們在 Makefile 中這樣寫,然后再 shell 命令行執行 make 命令,就可以看到打印出來的是 "a.o b.o d.o" ,實現了文件名后綴的替換。
表達式中使用了 "%" 這個字符,這個字符的含義就是自動匹配一個或多個字符。在開發的過程中,我們通常會使用這種方式來進行變量替換引用的操作。
2、變量的嵌套使用
變量的嵌套引用的具體含義是這樣的,我們可以在一個變量的賦值中引用其他的變量,並且引用變量的數量和和次數是不限制的。下面我們通過實例來說明一下。
實例:
foo:=test var:=$(foo) All: @echo $(var)
這種用法是最常見的使用方法,打印出 var 的值就是 test。我們可以認為是一層的嵌套引用。
22. Makefile控制函數error和warning
Makefile 中提供了兩個控制 make 運行方式的函數。其作用是當 make 執行過程中檢測到某些錯誤時為用戶提供消息,並且可以控制 make 執行過程是否繼續。這兩個函數是 "error" 和 "warning",我們來詳細的介紹一下這兩個函數。
$(error TEXT...)
函數說明如下:
- 函數功能:產生致命錯誤,並提示 "TEXT..." 信息給用戶,並退出 make 的執行。需要說明的是:"error" 函數是在函數展開時(函數被調用時)才提示信息並結束 make 進程。因此如果函數出現在命令中或者一個遞歸的變量定義時,讀取 Makefile 時不會出現錯誤。而只有包含 "error" 函數引用的命令被執行,或者定義中引用此函數的遞歸變量被展開時,才會提示知名信息 "TEXT..." 同時退出 make。
- 返回值:空
- 函數說明:"error" 函數一般不出現在直接展開式的變量定義中,否則在 make 讀取 Makefile 時將會提示致命錯誤。
實例:
ERROR1=1234 all: ifdef ERROR1 $(error error is $(ERROR1)) endif
make 讀取解析 Makefile 時,如果所起的變量名是已經定義好的"ERROR1",make 將會提示致命錯誤信息 "error is 1234" 並保存退出。
$(warning TEXT...)
函數說明如下:
- 函數功能:函數 "warning" 類似於函數 "error" ,區別在於它不會導致致命錯誤(make不退出),而只是提示 "TEXT...",make 的執行過程繼續。
- 返回值:空
- 函數說明:用法和 "error" 類似,展開過程相同。
23. Makefile中常見的錯誤信息
make 執行過程中所產生錯誤並不都是致命的,特別是在命令行之前存在 "-"、或者 make 使用 "-k" 選項執行時。
make 執行過程的致命錯誤都帶有前綴字符串 "***"。錯誤信息都有前綴,一種是執行程序名作為錯誤前綴(通常是 "make");另外一種是當 Makefile 本身存在語法錯誤無法被 make 解析並執行時,前綴包含了 Makefile 文件名和出現錯誤的行號。
Make
代碼變成可執行文件,叫做編譯(compile);先編譯這個,還是先編譯那個(即編譯的安排),叫做構建(build)。
Make是最常用的構建工具,誕生於1977年,主要用於C語言的項目。但是實際上 ,任何只要某個文件有變化,就要重新構建的項目,都可以用Make構建。
make只是一個根據指定的Shell命令進行構建的工具。它的規則很簡單,你規定要構建哪個文件、它依賴哪些源文件,當那些文件有變動時,如何重新構建它。