參考資料:陳浩,《跟我一塊兒寫makefile》 :http://blog.csdn.net/haoel/article/details/2886/算法
Makefile的格式和規則shell
target ... : prerequisites ...
command
...
...express
target是目標文件,Object File或者可執行文件,或者標籤。編程
prerequisites是生成target依賴的全部文件。編程語言
command是make須要執行的命令。(任意的Shell命令)ide
須要注意的是command前必須是一個[Tab]。函數
這是個文件的依賴關係:生成target依賴於prerequisites 裏面的文件,生成規則由command給出。實際上,Makefile中最核心的內容是,當prerequisites 中有文件比target新,就執行command中的指令。測試
make識別到一個XXX.o時,能夠自動將XXX.c加入到依賴文件中,而且cc –o XXX.c 也能推導出來。所以在書寫makefile的時候能夠省去那些能夠由makefile推導出來的東西。好比以上的makefile簡化以後:ui
objects = main.o kbd.o command.o display.o / edit : $(objects) main.o : defs.h .PHONY : clean |
能夠看出,每個XXX.o的依賴文件只列出了頭文件,下面的command也省略了,由於這兩個東西均可以被make自動推導出來。
每個makefile都應該有一個清理規則,clean老是應該放在makefile的最後部分。通常爲:
clean: |
更好的作法是:
.PHONY : clean |
.PHONY代表clean是一個僞目標,-rm前面的減號意思是當對某些文件操做失敗時,繼續後面的操做。
讀入全部的Makefile。
讀入被include的其它Makefile。
初始化文件中的變量。
推導隱晦規則,並分析全部規則。
爲全部的目標文件建立依賴關係鏈。
根據依賴關係,決定哪些目標要從新生成。
執行生成命令。
makefile的最開始部分應該是make的最終目標,通常狀況下一個makefile都只有一個最終目標。
當工程的文件放在不一樣的目錄時,寫makefile時須要指定搜尋的目錄和規則。
VPATH = ….
makefile中有一個特殊變量VPATH,經過指定VPATH告訴make在VPATH所指的目錄尋找依賴的文件(固然是在當前目錄沒有找到的前提下),多個目錄用冒號隔開。
vpath <pattern> <directories>
<pattern>須要包含「%」字符。「%」的意思是匹配零或若干字符,例如,「%.h」表示全部以「.h」結尾的文件。<pattern>指定了要搜索的文件集,而<directories>則指定了<pattern>的文件集的搜索的目錄,多個目錄用冒號隔開。
vpath和上面的VPATH並不相同,vpath是makefile中的關鍵字,它能夠指定不一樣的文件在不一樣的搜索目錄中,好比:
vpath %.h ../headers
表示在../headers目錄搜索.h頭文件。
前面提到過僞目標,僞目標其實和真正的目標區別不大,最大的區別就是僞目標只是一個便籤,不生成真正的目標文件。咱們可使用一個特殊的標記「.PHONY」來顯示地指明一個目標是「僞目標」,向make說明,無論是否有這個文件,這個目標就是「僞目標」。如:
.PHONY clean
僞目標能夠很是有用。以前咱們說過,makefile通常只有一個最終目標,就是其第一個目標,若是想一次性生成多個目標怎麼辦呢?這就能夠藉助於僞目標來實現。好比:
all : prog1 prog2 prog3 prog1 : prog1.o utils.o prog2 : prog2.o prog3 : prog3.o sort.o utils.o |
這裏的最終目標是一個僞目標all,在執行make 時候僞目標老是會被執行,所以依賴的三個文件會被生成。
再好比,咱們在清理的時候可能須要執行不一樣級別的清理,那麼也能夠用僞目標來區分。
上文提到要生成多個目標時候可使用僞目標來實現,然而實際上makefile的target能夠不止一個。有些狀況下,這一組目標有某個或者某些相同的依賴文件並且生成的規則類似,那麼就可使用makefile的多目標來生成。如:
bigoutput littleoutput : text.g |
bigoutput : text.g |
左邊的規則與右邊的等價。簡單解釋一下,因爲bigoutput和littleoutput都依賴文件text.g,且生成規則類似,則寫成下面一行的命令形式。其中-$(subst output,,$@)中的」$」表示要執行一個makefile函數,函數名是subst,後面都是參數。「$@」表示目標的集合。這個函數的做用是截取字符串。
在定義多目標規則時,利用靜態模式將更加有用。靜態模式的基本語法是:
<targets ...>: <target-pattern>: <prereq-patterns ...> |
targets定義了一系列的目標文件,能夠有通配符。是目標的一個集合。
target-parrtern是指明瞭targets的模式,也就是的目標集模式。
prereq-parrterns是目標的依賴模式,它對target-parrtern造成的模式再進行一次依賴目標的定義。
換一種更加通俗的說法,target定義的是一個目標的集合,裏面包含了不少的目標,而target-pattern定義的是本次命令執行的目標模式,也就是說從target目標集合裏選出匹配target-pattern模式的目標來做爲本次命令的真正目標。而prereq-patterns定義的是依賴文件的匹配模式。舉個簡單的例子,若是target-pattern爲「%.o」,那麼目標集合就是target中全部以[.o]爲後綴的目標文件。而若是prereq-patterns定義爲「%.c」,因而依賴的文件就是target-pattern匹配的全部的[.o]目標文件對應的[.c]文件。
一個例子:
objects = foo.o bar.o all: $(objects) $(objects): %.o: %.c |
由以前的分析能夠看出,target-pattern爲[%.o]匹配了objects中全部的[.o]目標文件,即foo.o bar.o,而[%.c]代表命令的依賴文件是foo.c bar.c。命令中的[$<]表示全部的依賴目標集(也就是foo.c bar.c),[$@]表示目標集(也就是foo.o bar.o)。
makefile的變量在定義時須要賦給一個初值,使用時前面要有一個$符合,通常的使用慣例是「${}」或「$()」,若要使用真正的「$」符號則表示爲「$$」。一個簡單的例子:
edit : main.o kbd.o command.o display.o / main.o : main.c defs.h |
這個makefile的edit依賴文件不少,能夠用一個變量來代替它。
objects = main.o kbd.o command.o display.o / |
因而,第一行就能夠變爲:
edit : $(objects) |
這樣爲書寫makefile,以及在大的工程中修改makefile帶了很大的便利,也讓makefile更加簡潔易懂。makefile中的目標、依賴、命令均可以用變量來表示。makefile定義變量的方法有三種,一種是像一般的編程語言同樣使用相似以下的方式。這種方式定義變量的好處在於能夠將變量的定義放在後面,換句話說,前面的命令可使用後面定義的變量。
objects = main.o kbd.o command.o display.o
另一種變量定義方式是使用「:=」操做符,這種定義方式不能使用後面定義的變量。例如:
x := foo |
等價於
y := foo bar |
而,
y := $(x) bar x := foo |
卻等價於
y := bar x := foo |
還有一種用於定義變量的操做符「?=」,
x ?= foo
表示若是x沒有定義過,那麼x = foo,不然什麼也不作。
變量值的替換的例子:
foo := a.o b.o c.o |
foo := a.o b.o c.o |
上面兩種表示的效果是同樣的——變量替換,讓$(bar)變量的值爲「a.c b.c c.c」,可是有一點點不一樣。左邊$(foo:.o=.c)表示的是,變量foo中全部.o結尾的字符串,替換成.c結尾的字符串。右邊$(foo:%.o=%.c)這種形式是靜態模式的表示形式,變量foo中匹配模式%.o的替換成了%.c,%是必需的。
咱們可使用「+=」操做符給變量追加值,即增長一個值。
objects = main.o foo.o bar.o utils.o
objects += another.o
define關鍵字能夠用來定義多行變量,如:
define two-lines |
以define開頭,以endef結尾。命令前面必需有[Tab],至關於 two-lines = foo $(bar) |
有一種很是有用的變量叫目標變量,定義語法和舉例以下:
<target ...> : <variable-assignment> <target ...> : overide <variable-assignment> |
prog : CFLAGS = -g prog.o : prog.c foo.o : foo.c bar.o : bar.c |
當設置了目標變量時,這個變量會做用到由這個目標所引起的全部的規則中去。如上例,prog : CFLAGS = -g中定義了目標變量CFLAGS,所以在全部由prog引發的命令中,這個CFLAGS都會起做用。target還能夠用模式變量。如:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
%.o : CFLAGS = -O
libs_for_gcc = -lgnu foo: $(objects) |
條件判斷由ifeq開始,後面括號裏是進行比較的兩個變量或者常量,ifeq ($(CC),gcc)表示若是變量CC是gcc的話,執行下一條命令,不然(else)執行else下的命令,判斷由endif結束。實際上,ifeq能夠由下面幾種方式來使用,而上例使用的是第一種方式。
ifeq (<arg1>, <arg2>) |
另一個條件判斷關鍵字是ifneq,意思很明白,和ifeq的剛好相反,可是用法相同。
第三個條件關鍵字是「ifdef」,
ifdef <variable-name>
若是變量<variable-name>的值非空,那到表達式爲真。不然,表達式爲假。實際上,ifdef就是測試變量是否有值。另外還有一個與之對應的ifndef,意思很是明確就很少說了。
在條件判斷中最好不要出現自動化變量(如」$@」),由於自動化變量在運行時纔會有值。
實際上在以前的例子中已經屢次提到過makefile的函數了。固然這個函數和咱們程序語言的函數有相同點也有很大的不一樣,makefile函數調用後有返回值,並且咱們可使用這個返回值,可是makefile的函數只有那麼多,不能由用戶本身來定義,不過也夠用了。函數的調用語法以下:
$(<function> <arguments1,arguments2,arguments3>) 或者使用{}括號 |
<function>是函數名,後面是參數列表,函數名和參數之間用空格隔開,參數之間用逗號隔開。
字符串處理函數:
$(subst <from>,<to>,<text>)
名稱:字符串替換函數——subst。 |
$(patsubst <pattern>,<replacement>,<text>)
名稱:模式字符串替換函數——patsubst。 返回:函數返回被替換事後的字符串。 |
$(strip <string>)
名稱:去空格函數——strip。 |
$(findstring <find>,<in>)
名稱:查找字符串函數——findstring。 |
$(filter <pattern...>,<text>)
名稱:過濾函數——filter。 |
$(filter-out <pattern...>,<text>)
名稱:反過濾函數——filter-out。 |
$(sort <list>)
名稱:排序函數——sort。 備註:sort函數會去掉<list>中相同的單詞。 |
$(word <n>,<text>)
名稱:取單詞函數——word。 |
$(wordlist <s>,<e>,<text>)
名稱:取單詞串函數——wordlist。 |
$(words <text>)
名稱:單詞個數統計函數——words。 備註:若是咱們要取<text>中最後的一個單詞,咱們能夠這樣:$(word $(words <text>),<text>)。 |
$(firstword <text>)
名稱:首單詞函數——firstword。 |
文件名操做函數:
$(dir <names...>)
名稱:取目錄函數——dir。 |
$(notdir <names...>)
名稱:取文件函數——notdir。 |
$(suffix <names...>)
名稱:取後綴函數——suffix。 |
$(basename <names...>)
名稱:取前綴函數——basename。 |
$(addsuffix <suffix>,<names...>)
名稱:加後綴函數——addsuffix。 |
$(addprefix <prefix>,<names...>)
名稱:加前綴函數——addprefix。 |
$(join <list1>,<list2>)
名稱:鏈接函數——join。 |
特殊函數:
$(foreach <var>,<list>,<text>)
foreach函數和shell中的for語句相似,用於循環控制。這個命令的解釋是:將<list>中的字符串逐一取出放入臨時變量<var>中(循環完成,<var>便不復存在),而後再執行<text>中包含的命令,最後將執行以後的結果返回。每次返回的結果由一個空格隔開,循環完成以後的返回值就是由空格隔開的一系列結果。
例如:
names := a b c d files := $(foreach n,$(names),$(n).o) |
$(name)中的單詞會被挨個取出,並存到變量「n」中,「$(n).o」每次根據「$(n)」計算出一個值,這些值以空格分隔,最後做爲foreach函數的返回,因此,$(files)的值是「a.o b.o c.o d.o」。
$(if <condition>,<then-part>,<else-part>)
if函數是一個條件控制語句,首先計算<condition>,若是<condition>返回非空字符,則計算<then-part>,不然計算<else-part>,固然和編程語言同樣<else-part>是可選的。最後函數返回計算部分的結果。
$(call <expression>,<parm1>,<parm2>,<parm3>...)
call函數計算<expression>的結果,<expression>中的變量,如$(1),$(2),$(3),會被parm1,parm2,parm3替換。最後函數返回<expression>的結果。
例如:
reverse = $(1) $(2) foo = $(call reverse,a,b) |
此時的foo的結果就是「a b」。
$(origin <variable>)
origin函數返回變量<variable>是在哪兒定義的。通常不在<variable>中使用$,由於此處指的是變量名。
函數的返回值有幾種狀況:
若是<variable>歷來沒有定義過,origin函數返回這個值「undefined」。
若是<variable>是一個默認的定義,好比「CC」這個變量,這種變量咱們將在後面講述。
若是<variable>是一個環境變量,而且當Makefile被執行時,「-e」參數沒有被打開。
若是<variable>這個變量被定義在Makefile中。
若是<variable>這個變量是被命令行定義的。
若是<variable>是被override指示符從新定義的。
若是<variable>是一個命令運行中的自動化變量。
$(shell <command> <var>)
shell函數的做用是在makefile中調用shell命令來處理變量,返回值就是shell命令執行的結果。好比:
files := $(shell echo *.c) |
files最後的值就是全部.c文件。
make的控制函數:
$(error <text ...>)
產生一個致命的錯誤,<text ...>是錯誤信息,並結束make。
$(warning <text ...>)
產生一個warning信息,並繼續執行make。
「all」
這個僞目標是全部目標的目標,其功能通常是編譯全部的目標。
「clean」
這個僞目標功能是刪除全部被make建立的文件。
「install」
這個僞目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。
「print」
這個僞目標的功能是例出改變過的源文件。
「tar」
這個僞目標功能是把源程序打包備份。也就是一個tar文件。
「dist」
這個僞目標功能是建立一個壓縮文件,通常是把tar文件壓成Z文件。或是gz文件。
「TAGS」
這個僞目標功能是更新全部的目標,以備完整地重編譯使用。
「check」和「test」
這兩個僞目標通常用來測試makefile的流程。
「-n」
「--just-print」
「--dry-run」
「--recon」
不執行參數,這些參數只是打印命令,無論目標是否更新,把規則和連帶規則下的命令打印出來,但不執行
「-t」
「--touch」
這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,make僞裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的狀態。
「-q」
「--question」
這個參數的行爲是找目標的意思,也就是說,若是目標存在,那麼其什麼也不會輸出,固然也不會執行編譯,若是目標不存在,其會打印出一條出錯信息。
「-W <file>」
「--what-if=<file>」
「--assume-new=<file>」
「--new-file=<file>」
這個參數須要指定一個文件。通常是是源文件(或依賴文件),Make會根據規則推導來運行依賴於這個文件的命令,通常來講,能夠和「-n」參數一同使用,來查看這個依賴文件所發生的規則命令。
「-b」
「-m」
這兩個參數的做用是忽略和其它版本make的兼容性。
「-B」
「--always-make」
認爲全部的目標都須要更新(重編譯)。
「-C <dir>」
「--directory=<dir>」
指定讀取makefile的目錄。若是有多個「-C」參數,make的解釋是後面的路徑之前面的做爲相對路徑,並以最後的目錄做爲被指定目錄。
「—debug[=<options>]」
輸出make的調試信息。它有幾種不一樣的級別可供選擇,若是沒有參數,那就是輸出最簡單的調試信息。下面是<options>的取值:
a —— 也就是all,輸出全部的調試信息。(會很是的多)
b —— 也就是basic,只輸出簡單的調試信息。即輸出不須要重編譯的目標。
v —— 也就是verbose,在b選項的級別之上。輸出的信息包括哪一個makefile被解析,不須要被重編譯的依賴文件(或是依賴目標)等。
i —— 也就是implicit,輸出因此的隱含規則。
j —— 也就是jobs,輸出執行規則中命令的詳細信息,如命令的PID、返回碼等。
m —— 也就是makefile,輸出make讀取makefile,更新makefile,執行makefile的信息。
「-d」
至關於「--debug=a」。
「-e」
「--environment-overrides」
指明環境變量的值覆蓋makefile中定義的變量的值。
「-f=<file>」
「--file=<file>」
「--makefile=<file>」
指定須要執行的makefile。
「-h」
「--help」
顯示幫助信息。
「-i」
「--ignore-errors」
在執行時忽略全部的錯誤。
「-I <dir>」
「--include-dir=<dir>」
指定一個被包含makefile的搜索目標。可使用多個「-I」參數來指定多個目錄。
「-j [<jobsnum>]」
「--jobs[=<jobsnum>]」
指同時運行命令的個數。
「-k」
「--keep-going」
出錯也不中止運行。
「-l <load>」
「--load-average[=<load]」
「—max-load[=<load>]」
指定make運行命令的負載。
「-n」
「--just-print」
「--dry-run」
「--recon」
僅輸出執行過程當中的命令序列,但並不執行。
「-o <file>」
「--old-file=<file>」
「--assume-old=<file>」
不從新生成的指定的<file>,即便這個目標的依賴文件新於它。
「-p」
「--print-data-base」
輸出makefile中的全部數據,包括全部的規則和變量。
「-r」
「--no-builtin-rules」
禁止make使用任何隱含規則。
「-R」
「--no-builtin-variabes」
禁止make使用任何做用於變量上的隱含規則。
「-s」
「--silent」
「--quiet」
在命令運行時不輸出命令的輸出。
「-S」
「--no-keep-going」
「--stop」
取消「-k」選項的做用
一、編譯C程序的隱含規則。
「<n>.o」的目標的依賴目標會自動推導爲「<n>.c」,而且其生成命令是「$(CC) –c $(CPPFLAGS) $(CFLAGS)」
二、編譯C++程序的隱含規則。
「<n>.o」的目標的依賴目標會自動推導爲「<n>.cc」或是「<n>.C」,而且其生成命令是「$(CXX) –c $(CPPFLAGS) $(CFLAGS)」。(建議使用「.cc」做爲C++源文件的後綴,而不是「.C」)
七、彙編和彙編預處理的隱含規則。
「<n>.o」 的目標的依賴目標會自動推導爲「<n>.s」,默認使用編譯品「as」,而且其生成命令是:「$(AS) $(ASFLAGS)」。「<n>.s」 的目標的依賴目標會自動推導爲「<n>.S」,默認使用C預編譯器「cpp」,而且其生成命令是:「$(AS) $(ASFLAGS)」。
八、連接Object文件的隱含規則。
「<n>」目標依賴於「<n>.o」,經過運行C的編譯器來運行連接程序生成(通常是「ld」),其生成命令是:「$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)」。這個規則對於只有一個源文件的工程有效,同時也對多個Object文件(由不一樣的源文件生成)的也有效。
隱含規則使用的變量:
一、關於命令的變量。
AR
函數庫打包程序。默認命令是「ar」。
AS
彙編語言編譯程序。默認命令是「as」。
CC
C語言編譯程序。默認命令是「cc」。
CXX
C++語言編譯程序。默認命令是「g++」。
CO
從 RCS文件中擴展文件程序。默認命令是「co」。
CPP
C程序的預處理器(輸出是標準輸出設備)。默認命令是「$(CC) –E」。
RM
刪除文件命令。默認命令是「rm –f」。
ARFLAGS
函數庫打包程序AR命令的參數。默認值是「rv」。
ASFLAGS
彙編語言編譯器參數。(當明顯地調用「.s」或「.S」文件時)。
CFLAGS
C語言編譯器參數。
CXXFLAGS
C++語言編譯器參數。
CPPFLAGS
C預處理器參數。( C 和 Fortran 編譯器也會用到)。
LDFLAGS
連接器參數。(如:「ld」)
所謂自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至全部的符合模式的文件都取完了。這種自動化變量只應出如今規則的命令中。
$@
表示規則中的目標文件集。在模式規則中,若是有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。
$%
僅當目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是"foo.a"。若是目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值爲空。
$<
依賴目標中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
$?
全部比目標新的依賴目標的集合。以空格分隔。
$^
全部的依賴目標的集合。以空格分隔。若是在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。
$+
這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目標。
$*
這個變量表示目標模式中"%"及其以前的部分。
$(@D)
表示"$@"的目錄部分(不以斜槓做爲結尾),若是"$@"值是"dir/foo.o",那麼"$(@D)"就是"dir",而若是"$@"中沒有包含斜槓的話,其值就是"."(當前目錄)。
$(@F)
表示"$@"的文件部分,若是"$@"值是"dir/foo.o",那麼"$(@F)"就是"foo.o","$(@F)"至關於函數"$(notdir $@)"。
"$(*D)"
"$(*F)"
和上面所述的同理,也是取文件的目錄部分和文件部分。對於上面的那個例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"
"$(%D)"
"$(%F)"
分別表示了函數包文件成員的目錄部分和文件部分。這對於形同"archive(member)"形式的目標中的"member"中包含了不一樣的目錄頗有用。
"$(<D)"
"$(<F)"
分別表示依賴文件的目錄部分和文件部分。
"$(^D)"
"$(^F)"
分別表示全部依賴文件的目錄部分和文件部分。(無相同的)
"$(+D)"
"$(+F)"
分別表示全部依賴文件的目錄部分和文件部分。(能夠有相同的)
"$(?D)"
"$(?F)"
分別表示被更新的依賴文件的目錄部分和文件部分。
好比咱們有一個目標叫 T。下面是搜索目標T的規則的算法。請注意,在下面,咱們沒有提到後綴規則,緣由是,全部的後綴規則在Makefile被載入內存時,會被轉換成模式規則。若是目標是"archive(member)"的函數庫文件模式,那麼這個算法會被運行兩次,第一次是找目標T,若是沒有找到的話,那麼進入第二次,第二次會把"member"看成T來搜索。
一、把T的目錄部分分離出來。叫D,而剩餘部分叫N。(如:若是T是"src/foo.o",那麼,D就是"src/",N就是"foo.o")
二、建立全部匹配於T或是N的模式規則列表。
三、若是在模式規則列表中有匹配全部文件的模式,如"%",那麼從列表中移除其它的模式。
四、移除列表中沒有命令的規則。
五、對於第一個在列表中的模式規則:
1)推導其"莖"S,S應該是T或是N匹配於模式中"%"非空的部分。
2)計算依賴文件。把依賴文件中的"%"都替換成"莖"S。若是目標模式中沒有包含斜框字符,而把D加在第一個依賴文件的開頭。
3)測試是否全部的依賴文件都存在或是理當存在。(若是有一個文件被定義成另一個規則的目標文件,或者是一個顯式規則的依賴文件,那麼這個文件就叫"理當存在")
4)若是全部的依賴文件存在或是理當存在,或是就沒有依賴文件。那麼這條規則將被採用,退出該算法。
六、若是通過第5步,沒有模式規則被找到,那麼就作更進一步的搜索。對於存在於列表中的第一個模式規則:
1)若是規則是終止規則,那就忽略它,繼續下一條模式規則。
2)計算依賴文件。(同第5步)
3)測試全部的依賴文件是否存在或是理當存在。
4)對於不存在的依賴文件,遞歸調用這個算法查找他是否能夠被隱含規則找到。
5)若是全部的依賴文件存在或是理當存在,或是就根本沒有依賴文件。那麼這條規則被採用,退出該算法。
七、若是沒有隱含規則可使用,查看".DEFAULT"規則,若是有,採用,把".DEFAULT"的命令給T使用。
一旦規則被找到,就會執行其至關的命令,而此時,咱們的自動化變量的值纔會生成。