Makefile學習

Makefile學習


參考自《跟我一起寫Makefile》陳皓


Makefile 的語法規則

基本語法

target ... : prerequisites ...
    command
    ...
    ...

翻譯成中文大概就是:

一系列目標文件:一系列依賴文件
    執行的一些命令

target 也就是一個目標文件,可以是 Object File ,也可以是執行文件。還可以是一個標簽。

prerequisites 就是要生成那個 target 所需要的文件或是目標。

command 也就是 make 需要執行的任意的 shell 命令。

默認情況下, make 命令會在當前目錄下按順序找尋文件名為"GNUmakefile" "makefile" "Makefile"的文件,并解釋這個文件。一般使用"Makefile"這個文件名。

如果要指定特定的 Makefile,你可以使用 make 的"-f"和"--file"參數,如:make -f Make.Linux 或 make --file Make.AIX。

更新函數庫文件

liba(a.o ...):a.o ...
    ar cr liba a.o ...

條件表達式

條件表達式的語法為:

<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else <conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中<conditional-directive>表示條件關鍵字,這個關鍵字有四種:

  • ifeq
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比較參數"arg1"和"arg2"的值是否相同。

  • ifneq
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

比較參數"arg1"和"arg2"的值是否相同,如果不同,則為真。

  • ifdef
ifdef <variable-name>

如果變量<variable-name>的值非空,那到表達式為真。否則,表達式為假。當然,<variable-name>同樣可以是一個函數的返回值。注意,ifdef 只是測試一個變量是否有值,其并不會把變量擴展到當前位置。

  • ifndef
ifndef <variable-name>

和 ifdef 是意思相反。

偽目標

"偽目標"并不是一個文件,只是一個標簽,由于"偽目標"不是文件,所以 make 無法生成它的依賴關系和決定它是否要執行。我們只有通過顯示地指明這個"目標"才能讓其生效。當然,"偽目標"的取名不能和文件名重名,不然其就失去了"偽目標"的意義了。

當然,為了避免和文件重名的這種情況,我們可以使用一個特殊的標記".PHONY"來顯示地指明一個目標是"偽目標",向 make 說明,不管是否有這個文件,這個目標就是"偽目標"。

.PHONY : clean

多目標

bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@

上述規則等價于:

bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.ggenerate text.g -little > littleoutput

靜態模式

<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...

targets 定義了一系列的目標文件,可以有通配符。是目標的一個集合。

target-parrtern 是指明了 targets 的模式,也就是的目標集模式。

prereq-parrterns 是目標的依賴模式,它對 target-parrtern 形成的模式再進行一次依賴目標的定義。

注釋

Makefile 中使用'#'字符作為注釋符,在'#'之后的是注釋部分,當你真正想用到'#'字符時,可以用反斜杠進行轉義,如'\#'。


Makefile 使用變量

普通變量

a = main.o # 定義變量a的值為main.o
var = aa

$a   # 調用變量
$(a) # 調用變量
${a}
$(var) # 調用變量
${var}

多行變量

define two-lines
echo foo
echo $(bar)
endef

自動化變量

  • $@ 表示規則中的目標文件集。在模式規則中,如果有多個目標,那么,"$@"就是匹配于目標中模式定義的集合。

  • $% 僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目標不是函數庫文件(Unix 下是[.a],Windows 下是[.lib]),那么,其值為空。

  • $< 依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那么"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。

  • $? 所有比目標新的依賴目標的集合。以空格分隔。

  • $^ 所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只保留一份。

  • $+ 這個變量很像"$^",也是所有依賴目標的集合。只是它不去除重復的依賴目標。

  • $* 這個變量表示目標模式中"%"及其之前的部分。如果目標是"dir/a.foo.b",并且目標的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。這個變量對于構造有關聯的文件名是比較有較。如果目標中沒有模式的定義,那么"$*"也就不能被推導出,但是,如果目標文件的后綴是 make 所識別的,那么"$*"就是除了后綴的那一部分。例如:如果目標是"foo.c",因為".c"是 make 所能識別的后綴名,所以,"$*"的值就是"foo"。這個特性是 GNU make 的,很有可能不兼容于其它版本的 make,所以,你應該盡量避免使用"$*",除非是在隱含規則或是靜態模式中。如果目標中的后綴是 make 所不能識別的,那么"$*"就是空值。

  • $(@D) 表示"$@"的目錄部分(不以斜杠作為結尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中沒有包含斜杠的話,其值就是"."(當前目錄)。

  • $(@F) 表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o"。

  • $(F)$(D)$(%D)$(%F)$(<D)$(<F)$(D)**、**$(F)$(+D)$(+F)$(?D)$(?F)** 同理分別表示對應文件的目錄部分和文件部分。

變量賦值

  • = 是最基本的賦值

  • := 是覆蓋之前的值

  • ?= 是如果沒有被賦值過就賦予等號后面的值

  • += 是追加等號后面的值


Makefile 使用函數

函數調用語法

$(<function> <arguments>)
${<function> <arguments>}

字符串處理函數

  • 字符串替換函數——subst
$(subst <from>, <to>, <text>)

功能:把字符串<text>中的<from>字符串替換成<to>。

返回:函數返回被替換過后的字符串。

  • 模式字符串替換函數——patsubst
$(patsubst <pattern>, <replacement>, <text>)

功能:查找<text>中的單詞(單詞以"空格"、"Tab"、"回車"或"換行"分隔)是否符合模式<pattern>,如果匹配的話,則以<replacement>替換。這里,<pattern>可以包括通配符"%",表示任意長度的字符串。如果<replacement>中也包含"%",那么,<replacement>中的這個"%"將是<pattern>中的那個"%"所代表的字符串。(可以用""來轉義,以"%"來表示真實含義的"%"字符)

返回:函數返回被替換過后的字符串。

  • 去空格函數——strip
$(strip <string>)

功能:去掉<string>字符串中開頭和結尾的空字符。

返回:返回被去掉空格的字符串值。

  • 查找字符串函數——findstring
$(findstring <find>, <in>)

功能:在字符串<in>中查找<find>字符串。

返回:如果找到,那么返回<find>,否則返回空字符串。

  • 過濾函數——filter
$(filter <pattern...>, <text>)

功能:以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。可以有多個模式。

返回:返回符合模式<pattern>的字符串。

  • 反過濾函數——filter-out
$(filter-out <pattern...>, <text>)

功能:以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞。可以有多個模式。

返回:返回不符合模式<pattern>的字符串。

  • 排序函數——sort
$(sort <list>)

功能:給字符串<list>中的單詞排序(升序)。

返回:返回排序后的字符串。

  • 取單詞函數——word
$(word <n>, <text>)

功能:取字符串<text>中第<n>個單詞。(從一開始)

返回:返回字符串<text>中第<n>個單詞。如果<n>比<text>中的單詞數要大,那么返回空字符串。

  • 取單詞串函數——wordlist
$(wordlist <s>, <e>, <text>)

功能:從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個數字。

返回:返回字符串<text>中從<s>到<e>的單詞字符串。如果<s>比<text>中的單詞數要大,那么返回空字符串。如果<e>大于<text>的單詞數,那么返回從<s>開始,到<text>結束的單詞串。

  • 單詞個數統計函數——words
$(words <text>)

功能:統計<text>中字符串中的單詞個數。

返回:返回<text>中的單詞數。

  • 首單詞函數——firstword
$(firstword <text>)

功能:取字符串<text>中的第一個單詞。

返回:返回字符串<text>的第一個單詞。

文件名操作函數

  • 取目錄函數——dir
$(dir <names...>)

功能:從文件名序列<names>中取出目錄部分。目錄部分是指最后一個反斜杠("/")之前的部分。如果沒有反斜杠,那么返回"./"。

返回:返回文件名序列<names>的目錄部分。

  • 取文件函數——notdir
$(notdir <names...>)

功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最后一個反斜杠("/")之后的部分。

返回:返回文件名序列<names>的非目錄部分。

  • 取后綴函數——suffix
$(suffix <names...>)

功能:從文件名序列<names>中取出各個文件名的后綴。

返回:返回文件名序列<names>的后綴序列,如果文件沒有后綴,則返回空字符串。

  • 取前綴函數——basename
$(basename <names...>)

功能:從文件名序列<names>中取出各個文件名的前綴部分。

返回:返回文件名序列<names>的前綴序列,如果文件沒有前綴,則返回空字符串。

  • 加后綴函數——addsuffix
$(addsuffix <suffix>, <names...>)

功能:把后綴<suffix>加到<names>中的每個單詞后面。

返回:返回加過后綴的文件名序列。

  • 加前綴函數——addprefix
$(addprefix <prefix>, <names...>)

功能:把前綴<prefix>加到<names>中的每個單詞前面。

返回:返回加過前綴的文件名序列。

  • 連接函數——join
$(join <list1>, <list2>)

功能:把<list2>中的單詞對應地加到<list1>的單詞后面。如果<list1>的單詞個數要比<list2>的多,那么,<list1>中的多出來的單詞將保持原樣。如果<list2>的單詞個數要比<list1>多,那么,<list2>多出來的單詞將被復制到<list2>中。

返回:返回連接過后的字符串。

foreach函數

$(foreach <var>, <list>, <text>)

功能:把參數<list>中的單詞逐一取出放到參數<var>所指定的變量中,然后再執行<text>所包含的表達式。每一次<text>會返回一個字符串,循環過程中, <text>的所返回的每個字符串會以空格分隔,最后當整個循環結束時,<text>所返回的每個字符串所組成的整個字符串(以空格分隔)將會是 foreach 函數的返回值。

返回:<text>所返回的每個字符串所組成的整個字符串(以空格分隔)。

if函數

$(if <condition>, <then-part>)
$(if <condition>, <then-part>, <else-part>)

功能:<condition>參數是 if 的表達式,如果其返回的為非空字符串,那么這個表達式就相當于返回真,于是,<then-part>會被計算,否則<else-part>會被計算。

返回:如果<condition>為真(非空字符串),那個<then-part>會是整個函數的返回值,如果<condition>為假(空字符串),那么<else-part>會是整個函數的返回值,此時如果<else-part>沒有被定義,那么,整個函數返回空字符串。

所以,<then-part>和<else-part>只會有一個被計算。

call函數

$(call <expression>, <parm1>, <parm2>, <parm3> ...)

功能:當 make 執行這個函數時,<expression>參數中的變量,如$(1),$(2),$(3)等,會被參數<parm1>,<parm2>,<parm3>依次取代。

返回:<expression>的返回值就是 call 函數的返回值。

origin函數

$(origin <variable>)

注意,<variable>是變量的名字,不應該是引用。所以你最好不要在<variable>中使用"$"字符。Origin 函數會以其返回值來告訴你這個變量的"出生情況",下面,是 origin函數的返回值:

  • "undefined"
    如果<variable>從來沒有定義過,origin 函數返回這個值"undefined"。

  • "default"
    如果<variable>是一個默認的定義,比如"CC"這個變量。

  • "environment"
    如果<variable>是一個環境變量,并且當 Makefile 被執行時,"-e"參數沒有被打開。

  • "file"
    如果<variable>這個變量被定義在 Makefile 中。

  • "command line"
    如果<variable>這個變量是被命令行定義的。

  • "override"
    如果<variable>是被 override 指示符重新定義的。

  • "automatic"
    如果<variable>是一個命令運行中的自動化變量。

shell函數

$(shell <shell script>)

功能:shell 函數也不像其它的函數。顧名思義,它的參數應該就是操作系統 Shell 的命令。它和反引號"`"是相同的功能。

返回:shell 函數把執行操作系統命令后的輸出作為函數返回。

注意,這個函數會新生成一個 Shell 程序來執行命令,所以你要注意其運行性能,如果你的 Makefile 中有一些比較復雜的規則,并大量使用了這個函數,那么對于你的系統性能是有害的。特別是 Makefile 的隱晦的規則可能會讓你的 shell 函數執行的次數比你想像的多得多。

控制make的函數

  • error函數
$(error <text ...>)

產生一個致命的錯誤,make 退出,<text ...>是錯誤信息。注意,error 函數不會在一被使用就會產生錯誤信息,所以如果你把其定義在某個變量中,并在后續的腳本中使用這個變量,那么也是可以的。

  • warning函數
$(warning <text ...>)

這個函數很像 error 函數,只是它并不會讓 make 退出,只是輸出一段警告信息,而 make 繼續執行。


Makefile 自動推導

只要 make 看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關系中,并且

$(CC) -c [.c] 

也會被推導出來


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容