makefile
了解兩個(gè)概念一個(gè)是?標(biāo)(target),另?個(gè)就是依賴(dependency)。?標(biāo)就是指要?什么,或說運(yùn)? make 后?成什么,?依賴是告訴 make 如何去做以實(shí)現(xiàn)?標(biāo)。在 Makefile 中,?標(biāo)和依賴是通過規(guī)則(rule)來表達(dá)的。


目標(biāo)
首次編寫makefile

上面Makefile 中的 all 就是我們 的?標(biāo),?標(biāo)放在‘:’的前?,其名字可以是由字?和下劃線‘_’組成 。echo “Hello World”就是?成?標(biāo)的命令,這些命令可以是任何你可以在你的環(huán)境中運(yùn)?的命令以及 make 所定義的函數(shù)等等。all ?標(biāo)的定義,其實(shí)是定義了如何?成 all ?標(biāo),這我們也稱之為規(guī)則.


?個(gè) Makefile 中可以定義多個(gè)?標(biāo)。調(diào)? make 命令時(shí),我們得告訴它我們的?標(biāo)是什么,即要它?什么。當(dāng)沒有指明具體的?標(biāo)是什么 時(shí),那么 make 以 Makefile ?件中定義的第?個(gè)?標(biāo)作為這次運(yùn)?的?標(biāo)。這“第?個(gè)”?標(biāo)也稱之 為默認(rèn)?標(biāo)(和是不是all沒有關(guān)系)。當(dāng) make 得到?標(biāo)后,先找到定義?標(biāo)的規(guī)則,然后運(yùn)?規(guī)則中的命令來達(dá)到構(gòu)建?標(biāo)的?的。
makefile中取消多余的命令行顯示
在上面的指令中,多了很多的echo "......"的內(nèi)容,這部分不是我們所期望的,如果要去掉,需要對上面的makefile進(jìn)行一個(gè)改動(dòng),也就是在命令前加上一個(gè)@,這個(gè)符號就是告訴make,在運(yùn)行的時(shí)候這一行命令不顯示出來。


對makefile進(jìn)行如下的改動(dòng),在all的后面加上test


此時(shí)test也被構(gòu)建了。
依賴
如上面的makefile,all ?標(biāo)后?的 test 是告訴 make,all ?標(biāo)依賴 test ?標(biāo),這?依賴?標(biāo)在 Makefile 中?被稱之為先決條件。出現(xiàn)這種?標(biāo)依賴關(guān)系時(shí),make?具會按 從左到右的先后順序先構(gòu)建規(guī)則中所依賴的每?個(gè)?標(biāo)。如果希望構(gòu)建 all ?標(biāo),那么make 會在構(gòu)建它之 前得先構(gòu)建 test ?標(biāo),這就是為什么我們稱之為先決條件的原因。

規(guī)則
?個(gè)規(guī)則是由?標(biāo)(targets)、先決條件(prerequisites)以及命令(commands)所組成的。
需要指出的是,?標(biāo)和先決條件之間表達(dá)的就是依賴關(guān)系(dependency),這種依賴關(guān)系指明在構(gòu)建?標(biāo)之前,必須保證先決條件先滿?(或構(gòu)建)。?先決條件可以是其它的?標(biāo),當(dāng)先決條件是?標(biāo)時(shí),其必須先被構(gòu)建出來。還有就是?個(gè)規(guī)則中?標(biāo)可以有多個(gè),當(dāng)存在多個(gè)?標(biāo),且這?規(guī)則是 Makefile 中的第?個(gè)規(guī)則時(shí),如果我們運(yùn)? make 命令不帶任何?標(biāo),那么規(guī)則中的第?個(gè)?標(biāo)將被視為是缺省?標(biāo)。
規(guī)則的功能就是指明 make 什么時(shí)候以及如何來為我們重新創(chuàng)建?標(biāo),在 Hello World 例?中,不論我們 在什么時(shí)候運(yùn)? make 命令(帶?標(biāo)或是不帶?標(biāo)),其都會在終端上打印出信息來,和我們采? make 進(jìn)?代碼編譯時(shí)的表現(xiàn)好象有些不同。當(dāng)采? Makefile 來編譯程序時(shí),如果兩次編譯之間沒有任何代碼 的改動(dòng),理論上說來,我們是不希望看到 make 會有什么動(dòng)作的,只需說“?標(biāo)是最新的”,?我們的最終 ?標(biāo)也是希望構(gòu)建出?個(gè)“聰明的” Makefile 的。與 Hello World 相?不同的是,采? Makefile 來進(jìn)? 代碼編譯時(shí),Makefile 中所存在的先決條件都是具體的程序?件,后?我們會看到。

規(guī)則語法:

如上, all 是?標(biāo),test 是 all ?標(biāo)的依賴?標(biāo),?@echo “Hello World”則是?于?成 all ?標(biāo)的命令。
makefile的原理
foo.c

main.c

main.c

makefile

三段代碼生成的依賴樹

編譯

上面的展示了測試結(jié)果,注意到了第?次編譯并沒有構(gòu)建?標(biāo)?件的動(dòng)作嗎?但為什么有構(gòu)建simple可執(zhí)?程序的動(dòng)作呢?為了明?為什么,我們需要了解 make 是如何決定哪些?標(biāo)(這?是?件)是需要重新編譯的。為什么 make會知道我們并沒有改變 main.c 和 foo.c 呢?答案很簡單,通過?件的時(shí)間戳!當(dāng) make 在運(yùn)??個(gè)規(guī)則時(shí),我們前?已經(jīng)提到 了?標(biāo)和先決條件之間的依賴關(guān)系,make 在檢查?個(gè)規(guī)則時(shí),采?的?法是:如果先決條件中相關(guān)的?件的時(shí)間戳?于?標(biāo)的時(shí)間戳,即先決條件中的?件??標(biāo)更新,則知道有變化,那么需要運(yùn)?規(guī)則當(dāng)中 的命令重新構(gòu)建?標(biāo)。這條規(guī)則會運(yùn)?到所有與我們在 make時(shí)指定的?標(biāo)的依賴樹中的每?個(gè)規(guī)則。?如,對于 simple 項(xiàng)?,其依賴樹中包括三個(gè)規(guī)則,make 會檢查所有三個(gè)規(guī)則當(dāng)中的?標(biāo)(?件)與先決條件(?件)之間的時(shí)間先后關(guān)系,從?來決定是否要重新創(chuàng)建規(guī)則中的?標(biāo)。(什么是時(shí)間戳?xí)r間戳是使用數(shù)字簽名技術(shù)產(chǎn)生的數(shù)據(jù),簽名的對象包括了原始文件信息、簽名參數(shù)、簽名時(shí)間等信息。時(shí)間戳系統(tǒng)用來產(chǎn)生和管理時(shí)間戳,對簽名對象進(jìn)行數(shù)字簽名產(chǎn)生時(shí)間戳,以證明原始文件在簽名時(shí)間之前已經(jīng)存在。時(shí)間戳(timestamp),通常是一個(gè)字符序列,唯一地標(biāo)識某一刻的時(shí)間。)
第二次構(gòu)建的時(shí)候?yàn)槭裁磗imple會被重新構(gòu)建?
是因?yàn)閟imple文件不存在,我們在這次構(gòu)建的目標(biāo)是all,而all在我們編譯的過成中并不生成,所以第二次make的時(shí)候找不到,所以又重新編譯了一遍。
修改makefile

第二次編譯時(shí)不需要重新生成

一個(gè)文件是否改變不是看這個(gè)文件的大小是否改變,而是看這個(gè)文件的時(shí)間戳是否發(fā)生了變化??梢灾苯邮褂胻ouch指令對文件的時(shí)間戳進(jìn)行修改。

這時(shí)候就會進(jìn)行重新編譯
假目標(biāo)

如果我們的創(chuàng)建了一個(gè)clean文件之后,繼續(xù)去運(yùn)行make clean,這時(shí)候不是按照我們前面運(yùn)行的make clean進(jìn)行清理文件。
為什么出現(xiàn)上面的原因?
因?yàn)檫@個(gè)時(shí)候make 將clean單程是一個(gè)文件,并且在當(dāng)前的目錄下找到了這個(gè)文件,再加上clean目標(biāo)沒有任何的先決條件,這時(shí)候進(jìn)行make clean時(shí),系統(tǒng)會認(rèn)為clean是最新的
如何解決上面的問題?使用假目標(biāo),假目標(biāo)最從常用清凈就是避免所定義的目標(biāo)和的已經(jīng)存在文件是從重名的情況,假?標(biāo)可以采?.PHONY 關(guān)鍵字來定義,需要注意的是其必須是?寫字?。使用假目標(biāo)修改makefile


采?.PHONY 關(guān)鍵字聲明?個(gè)?標(biāo)后,make 并不會將其當(dāng)作?個(gè)?件來處理,?只是當(dāng)作?個(gè)概念上的?標(biāo)。對于假?標(biāo),我們可以想像的是由于并不與?件關(guān)聯(lián),所以每?次 make 這個(gè)假?標(biāo)時(shí),其所在的規(guī)則中的命令都會被執(zhí)?。
變量


變量的使用可以提高makefile的可維護(hù)性。?個(gè)變量的定義很簡單,就是?個(gè)名字(變量名)后?跟上?個(gè)等號,然后在等號的后?放這個(gè)變量所期望的值。對于變量的引?,則需要采?$(變量名)或者${變量名}這種模式。在這個(gè) Makefile 中,我們引?了 CC 和 RM 兩個(gè)變量,?個(gè)?于保存編譯器名,?另?個(gè)?于指示刪除?件的命令是什么。還有就是引?了 EXE 和 OBJS 兩個(gè)變量,?個(gè)?于存放可執(zhí)??件名,可另?個(gè)則?于放置所有的?標(biāo)?件名。采?變量的話,當(dāng)我們需要更改編譯器時(shí),只需更改變量賦值的地?,?常?便,如果不采?變量,那我們得更改每?個(gè)使?編譯器的地?,很是麻煩。
自動(dòng)變量
對于每?個(gè)規(guī)則,?標(biāo)和先決條件的名字會在規(guī)則的命令中多次出現(xiàn),每?次出現(xiàn)都是?種麻煩,更為麻煩的是,如果改變了?標(biāo)或是依賴的名,那得在命令中全部跟著改。有沒有簡化這種更改的?法呢?這我們需要?到 Makefile 中的?動(dòng)變量,最常用包括:
$@?于表示?個(gè)規(guī)則中的?標(biāo)。當(dāng)我們的?個(gè)規(guī)則中有多個(gè)?標(biāo)時(shí),$@所指的是其中任何造成命令被運(yùn)?的?標(biāo)。
$^則表示的是規(guī)則中的所有先擇條件。
$<表示的是規(guī)則中的第?個(gè)先決條件。


在 Makefile 中‘$’具有特殊的意思,因此,如果想采? echo 輸出‘$’,則必需?兩個(gè)連著的‘$’。還有就是,$@對于 Shell 也有特殊的意思,我們需要在“$$@”之前再加?個(gè)脫字符‘\’。


特殊變量
MAKE變量
它表示的是make 命令名是什么。當(dāng)我們需要在 Makefile 中調(diào)?另?個(gè) Makefile 時(shí)需要?到這個(gè)變量,采?這種?式,有利于寫?個(gè)容易移植的 Makefile。


MAKECMDGOALS變量
它表示的是當(dāng)前?戶所輸?的 make ?標(biāo)是什么。

MAKECMDGOALS 指的是?戶輸?的?標(biāo),當(dāng)我們只運(yùn)? make 命令時(shí),雖然根據(jù)Makefile 的語法,第?個(gè)?標(biāo)將成為缺省?標(biāo),即 all ?標(biāo),但 MAKECMDGOALS 仍然是空,?不是all,這?點(diǎn)我們需要注意。
遞歸擴(kuò)展變量
示例了使?等號進(jìn)?變量定義和賦值,對于這種只??個(gè)“=”符號定義的變量,我們稱之為遞歸擴(kuò)展變量(recursively expanded variable)。


除了遞歸擴(kuò)展變量還有?種變量稱之為簡單擴(kuò)展變量(simply expanded variables),是?“:=”操作符來定義的。對于這種變量,make 只對其進(jìn)??次掃描和替換。


另外還有一種條件賦值符“?=”,條件賦值的意思是當(dāng)變量以前沒有定義時(shí),就定義它并且將左邊的值賦值給它,如果已經(jīng)定義了那么就不再改變其值。條件賦值類似于提供了給變量賦缺省值的功能。


此外,還有"+="操作符,對變量進(jìn)?賦值的?法


override指令
在設(shè)計(jì) Makefile 時(shí),我們并不希望?戶將我們在 Makefile 中定義的某個(gè)變量覆蓋掉,那就得? override 指令了。


模式
如果對于每?個(gè)?標(biāo)?件都得寫?個(gè)不同的規(guī)則來描述,那會是?種“體?活”,太繁了!對于?個(gè)?型項(xiàng)?,就更不?說了。Makefile 中的模式就是?來解決我們的這種煩惱的。

與 simple 項(xiàng)?前?版本的 Makefile 相?,最為直觀的改變就是從?條構(gòu)建?標(biāo)?件的規(guī)則變成了?條。模式類似于我們在 Windows 操作系統(tǒng)中所使?的通配符,當(dāng)然是?“%”?不是“*”。采?了模式以后,不論有多少個(gè)源?件要編譯,我們都是應(yīng)?同?個(gè)模式規(guī)則的,很顯然,這??的簡化了我們的?作。使?了模式規(guī)則以后,你同樣可以?這個(gè) Makefile 來編譯或是清除 simple 項(xiàng)?,這與前?版本在功能上是完全?樣的。
函數(shù)
函數(shù)是 Makefile 中的另?個(gè)利器,現(xiàn)在我們看?看采?函數(shù)如何來簡化 simple 項(xiàng)?的 Makefile。對于simple 項(xiàng)?的 Makefile,盡管我們使?了模式規(guī)則,但還有?件?較惱?的事,我們得在這個(gè)Makefile中指明每?個(gè)需要被編譯的源程序。對于?個(gè)源程序?件?較多的項(xiàng)?,如果每增加或是刪除?個(gè)?件都得更新 Makefile,其?作量也不可?視!
采?了 wildcard 和 patsubst 兩個(gè)函數(shù)后 simple 項(xiàng)?的 Makefile。可以先?它來編譯?下 simple 項(xiàng)?代碼以驗(yàn)證其功能性。需要注意的是函數(shù)的語法形式很是特別,對于我們來說只要記住其形式就?了。

現(xiàn)在,我們來模擬增加?個(gè)源?件的情形,看?看如果我們增加?個(gè)?件,在 Makefile 不做任何更改的情況下其是否仍能正常的?作。增加?件的?式仍然是采? touch 命令,通過 touch 命令?成?個(gè)內(nèi)容是空的 bar.c 源?件,然后再運(yùn)? make 和 make clean。


addprefix函數(shù)
addprefix 函數(shù)是?來在給字符串中的每個(gè)?串前加上?個(gè)前綴,其形式是:$(addprefix prefix, names...)


filter函數(shù)
filter 函數(shù)?于從?個(gè)字符串中,根據(jù)模式得到滿?模式的字符串,其形式是:$(filter pattern..., text)


結(jié)果來看,經(jīng)過 filter 函數(shù)的調(diào)?以后,source變量中只存在.c ?件和.s ?件了,?.h?件則則被過濾掉了。
filter-out函數(shù)
filter-out 函數(shù)?于從?個(gè)字符串中根據(jù)模式濾除?部分字符串,其形式是:$(filter-out pattern..., text)


patsubst函數(shù)
patsubst 函數(shù)是?來進(jìn)?字符串替換的,其形式是:$(patsubst pattern, replacement, text)


上述代碼中 mixed 變量中包括了.c ?件也包括了.o ?件,采?patsubst 函數(shù)進(jìn)?字符串替換時(shí),我們希望將所有的.c ?件都替換成.o ?件。上圖是最后的運(yùn)?結(jié)果。
strip
strip 函數(shù)?于去除變量中的多余的空格,其形式是:$(strip string)


wildcard函數(shù)
wildcard 是通配符函數(shù),通過它可以得到我們所需的?件,這個(gè)函數(shù)類似我們在 Windows 或Linux 命
令?中的“*”。其形式是:$(wildcard pattern)


makefile拔高
創(chuàng)建目錄
毫?疑問,我們在編譯項(xiàng)?之前希望?于存放?件的?錄先準(zhǔn)備好,當(dāng)然,我們可以在編譯之前通過?動(dòng)來創(chuàng)建所需的?錄,但這?我們希望采??動(dòng)的?式。makefile的依賴樹的樣子是這樣的。

這個(gè)依賴圖從概念上說來是對的,但從 Makefile 的實(shí)現(xiàn)上存在?些問題。我們說 all 是?個(gè)?標(biāo),如果 all 直接依賴 objs 和 exes ?錄的話,那應(yīng)該如何創(chuàng)建?錄呢?首先寫一個(gè)makefile【注意代碼的最后一行不能換行,表示一個(gè)依賴】


改進(jìn)依賴關(guān)系圖

改進(jìn)上面的makefile


在這個(gè) Makefile 中,需要注意的是 OBJS 變量即是?個(gè)依賴?標(biāo)也是?個(gè)?錄,在不同的場合其意思是不同的。?如,第?次 make 時(shí),由于 objs 和 exes ?錄都不存在,所以 all ?標(biāo)將它們視作是?個(gè)先決條件或者說是依賴?標(biāo),接著 Makefile 先根據(jù)?錄構(gòu)建規(guī)則構(gòu)建 objs 和 exes ?標(biāo),即Makefile 中的第?條規(guī)則就被派上了?場。構(gòu)建?錄時(shí),第?條規(guī)則中的命令被執(zhí)?,即真正的創(chuàng)建了 objs 和 exes ?錄。當(dāng)我們第?次進(jìn)? make 時(shí),此時(shí),make 仍以 objs 和 exes 為?標(biāo),但從?錄構(gòu)建規(guī)則中發(fā)現(xiàn),這兩個(gè)?標(biāo)并沒有依賴關(guān)系,?且能從當(dāng)前?錄中找到 objs 和 exes ?錄,即認(rèn)為 objs 和 exes ?標(biāo)都是最新的,所以不?再運(yùn)??錄構(gòu)建規(guī)則中的命令來創(chuàng)建?錄。
更新后代碼的依賴樹關(guān)系

接下來也得為 Makefile 創(chuàng)建?個(gè) clean ?標(biāo),專??來刪除所?成的?標(biāo)?件和可執(zhí)??件。加 clean 規(guī)則還是相當(dāng)簡單,需要再增加了兩個(gè)變量,?個(gè)是RM,另?個(gè)則是 RMFLAGS。


增加頭文件


將文件放進(jìn)目錄
為了將?標(biāo)?件或是可執(zhí)?程序分別放?所創(chuàng)建的 objs 和 exes ?錄中,我們需要?到 Makefile中的?個(gè)函數(shù) —— addprefix。對上面的makefile進(jìn)行修改。


最?的變化除了增加了對于 addprefix 函數(shù)的運(yùn)?為每?個(gè)?標(biāo)?件加上“objs/”前綴外,還有?個(gè)很?的變化是,我們需要在構(gòu)建?標(biāo)?件的模式規(guī)則中的?標(biāo)前也加上“objs/”前綴,即增加“$(DIR_OBJS)/”前綴。之所以要加上,是因?yàn)橐?guī)則的命令中的-o 選項(xiàng)需要以它作為?標(biāo)?件的最終?成位置,還有就是因?yàn)?OBJS 也加上了前綴,?要使得 Makefile 中的?標(biāo)創(chuàng)建規(guī)則被運(yùn)?,也需要采?相類似的格式,即前?有“objs/”。此外,由于改動(dòng)后的 Makefile 會將所有的?標(biāo)?件放? objs ?錄當(dāng)中,?我們的 clean 規(guī)則中的命令包含將 objs ?錄刪除的操作,所以我們可以去除命令中對 OBJS 中?件的刪除。這導(dǎo)致的改動(dòng)就是 Makefile 中的最后??中刪除了$(OBJS)。同樣的方法將 test 放入到 exes 文件夾中。