|
代碼變成可執(zhí)行文件,叫做編譯(compile);先編譯這個(gè),還是先編譯那個(gè)(即編譯的安排),叫做構(gòu)建(build)。 Make是最常用的構(gòu)建工具,誕生于1977年,主要用于C語(yǔ)言的項(xiàng)目。但是實(shí)際上 ,任何只要某個(gè)文件有變化,就要重新構(gòu)建的項(xiàng)目,都可以用Make構(gòu)建。 本文介紹Make命令的用法,從簡(jiǎn)單的講起,不需要任何基礎(chǔ),只要會(huì)使用命令行,就能看懂。我的參考資料主要是Isaac Schlueter的《Makefile文件教程》和《GNU Make手冊(cè)》。 (題圖:攝于博茲賈阿達(dá)島,土耳其,2013年7月) 一、Make的概念Make這個(gè)詞,英語(yǔ)的意思是"制作"。Make命令直接用了這個(gè)意思,就是要做出某個(gè)文件。比如,要做出文件a.txt,就可以執(zhí)行下面的命令。
但是,如果你真的輸入這條命令,它并不會(huì)起作用。因?yàn)镸ake命令本身并不知道,如何做出a.txt,需要有人告訴它,如何調(diào)用其他命令完成這個(gè)目標(biāo)。 比如,假設(shè)文件 a.txt 依賴于 b.txt 和 c.txt ,是后面兩個(gè)文件連接(cat命令)的產(chǎn)物。那么,make 需要知道下面的規(guī)則。
也就是說,make a.txt 這條命令的背后,實(shí)際上分成兩步:第一步,確認(rèn) b.txt 和 c.txt 必須已經(jīng)存在,第二步使用 cat 命令 將這個(gè)兩個(gè)文件合并,輸出為新文件。 像這樣的規(guī)則,都寫在一個(gè)叫做Makefile的文件中,Make命令依賴這個(gè)文件進(jìn)行構(gòu)建。Makefile文件也可以寫為makefile, 或者用命令行參數(shù)指定為其他文件名。
上面代碼指定make命令依據(jù)rules.txt文件中的規(guī)則,進(jìn)行構(gòu)建。 總之,make只是一個(gè)根據(jù)指定的Shell命令進(jìn)行構(gòu)建的工具。它的規(guī)則很簡(jiǎn)單,你規(guī)定要構(gòu)建哪個(gè)文件、它依賴哪些源文件,當(dāng)那些文件有變動(dòng)時(shí),如何重新構(gòu)建它。 二、Makefile文件的格式構(gòu)建規(guī)則都寫在Makefile文件里面,要學(xué)會(huì)如何Make命令,就必須學(xué)會(huì)如何編寫Makefile文件。 2.1 概述Makefile文件由一系列規(guī)則(rules)構(gòu)成。每條規(guī)則的形式如下。
上面第一行冒號(hào)前面的部分,叫做"目標(biāo)"(target),冒號(hào)后面的部分叫做"前置條件"(prerequisites);第二行必須由一個(gè)tab鍵起首,后面跟著"命令"(commands)。 "目標(biāo)"是必需的,不可省略;"前置條件"和"命令"都是可選的,但是兩者之中必須至少存在一個(gè)。 每條規(guī)則就明確兩件事:構(gòu)建目標(biāo)的前置條件是什么,以及如何構(gòu)建。下面就詳細(xì)講解,每條規(guī)則的這三個(gè)組成部分。 2.2 目標(biāo)(target)一個(gè)目標(biāo)(target)就構(gòu)成一條規(guī)則。目標(biāo)通常是文件名,指明Make命令所要構(gòu)建的對(duì)象,比如上文的 a.txt 。目標(biāo)可以是一個(gè)文件名,也可以是多個(gè)文件名,之間用空格分隔。 除了文件名,目標(biāo)還可以是某個(gè)操作的名字,這稱為"偽目標(biāo)"(phony target)。
上面代碼的目標(biāo)是clean,它不是文件名,而是一個(gè)操作的名字,屬于"偽目標(biāo) ",作用是刪除對(duì)象文件。
但是,如果當(dāng)前目錄中,正好有一個(gè)文件叫做clean,那么這個(gè)命令不會(huì)執(zhí)行。因?yàn)镸ake發(fā)現(xiàn)clean文件已經(jīng)存在,就認(rèn)為沒有必要重新構(gòu)建了,就不會(huì)執(zhí)行指定的rm命令。 為了避免這種情況,可以明確聲明clean是"偽目標(biāo)",寫法如下。
聲明clean是"偽目標(biāo)"之后,make就不會(huì)去檢查是否存在一個(gè)叫做clean的文件,而是每次運(yùn)行都執(zhí)行對(duì)應(yīng)的命令。像.PHONY這樣的內(nèi)置目標(biāo)名還有不少,可以查看手冊(cè)。 如果Make命令運(yùn)行時(shí)沒有指定目標(biāo),默認(rèn)會(huì)執(zhí)行Makefile文件的第一個(gè)目標(biāo)。
上面代碼執(zhí)行Makefile文件的第一個(gè)目標(biāo)。 2.3 前置條件(prerequisites)前置條件通常是一組文件名,之間用空格分隔。它指定了"目標(biāo)"是否重新構(gòu)建的判斷標(biāo)準(zhǔn):只要有一個(gè)前置文件不存在,或者有過更新(前置文件的last-modification時(shí)間戳比目標(biāo)的時(shí)間戳新),"目標(biāo)"就需要重新構(gòu)建。
上面代碼中,構(gòu)建 result.txt 的前置條件是 source.txt 。如果當(dāng)前目錄中,source.txt 已經(jīng)存在,那么
上面代碼中,source.txt后面沒有前置條件,就意味著它跟其他文件都無關(guān),只要這個(gè)文件還不存在,每次調(diào)用
上面命令連續(xù)執(zhí)行兩次 如果需要生成多個(gè)文件,往往采用下面的寫法。
上面代碼中,source 是一個(gè)偽目標(biāo),只有三個(gè)前置文件,沒有任何對(duì)應(yīng)的命令。
執(zhí)行
2.4 命令(commands)命令(commands)表示如何更新目標(biāo)文件,由一行或多行的Shell命令組成。它是構(gòu)建"目標(biāo)"的具體指令,它的運(yùn)行結(jié)果通常就是生成目標(biāo)文件。 每行命令之前必須有一個(gè)tab鍵。如果想用其他鍵,可以用內(nèi)置變量.RECIPEPREFIX聲明。
上面代碼用.RECIPEPREFIX指定,大于號(hào)(>)替代tab鍵。所以,每一行命令的起首變成了大于號(hào),而不是tab鍵。 需要注意的是,每行命令在一個(gè)單獨(dú)的shell中執(zhí)行。這些Shell之間沒有繼承關(guān)系。
上面代碼執(zhí)行后(
另一個(gè)解決辦法是在換行符前加反斜杠轉(zhuǎn)義。
最后一個(gè)方法是加上
三、Makefile文件的語(yǔ)法3.1 注釋井號(hào)(#)在Makefile中表示注釋。
3.2 回聲(echoing)正常情況下,make會(huì)打印每條命令,然后再執(zhí)行,這就叫做回聲(echoing)。
執(zhí)行上面的規(guī)則,會(huì)得到下面的結(jié)果。
在命令的前面加上@,就可以關(guān)閉回聲。
現(xiàn)在再執(zhí)行 由于在構(gòu)建過程中,需要了解當(dāng)前在執(zhí)行哪條命令,所以通常只在注釋和純顯示的echo命令前面加上@。
3.3 通配符通配符(wildcard)用來指定一組符合條件的文件名。Makefile 的通配符與 Bash 一致,主要有星號(hào)(*)、問號(hào)(?)和 [...] 。比如, *.o 表示所有后綴名為o的文件。
3.4 模式匹配Make命令允許對(duì)文件名,進(jìn)行類似正則運(yùn)算的匹配,主要用到的匹配符是%。比如,假定當(dāng)前目錄下有 f1.c 和 f2.c 兩個(gè)源碼文件,需要將它們編譯為對(duì)應(yīng)的對(duì)象文件。
等同于下面的寫法。
使用匹配符%,可以將大量同類型的文件,只用一條規(guī)則就完成構(gòu)建。 3.5 變量和賦值符Makefile 允許使用等號(hào)自定義變量。
上面代碼中,變量 txt 等于 Hello World。調(diào)用時(shí),變量需要放在 $( ) 之中。 調(diào)用Shell變量,需要在美元符號(hào)前,再加一個(gè)美元符號(hào),這是因?yàn)镸ake命令會(huì)對(duì)美元符號(hào)轉(zhuǎn)義。
有時(shí),變量的值可能指向另一個(gè)變量。
上面代碼中,變量 v1 的值是另一個(gè)變量 v2。這時(shí)會(huì)產(chǎn)生一個(gè)問題,v1 的值到底在定義時(shí)擴(kuò)展(靜態(tài)擴(kuò)展),還是在運(yùn)行時(shí)擴(kuò)展(動(dòng)態(tài)擴(kuò)展)?如果 v2 的值是動(dòng)態(tài)的,這兩種擴(kuò)展方式的結(jié)果可能會(huì)差異很大。 為了解決類似問題,Makefile一共提供了四個(gè)賦值運(yùn)算符 (=、:=、?=、+=),它們的區(qū)別請(qǐng)看StackOverflow。
3.6 內(nèi)置變量(Implicit Variables)Make命令提供一系列內(nèi)置變量,比如,$(CC) 指向當(dāng)前使用的編譯器,$(MAKE) 指向當(dāng)前使用的Make工具。這主要是為了跨平臺(tái)的兼容性,詳細(xì)的內(nèi)置變量清單見手冊(cè)。
3.7 自動(dòng)變量(Automatic Variables)Make命令還提供一些自動(dòng)變量,它們的值與當(dāng)前規(guī)則有關(guān)。主要有以下幾個(gè)。 (1)$@ $@指代當(dāng)前目標(biāo),就是Make命令當(dāng)前構(gòu)建的那個(gè)目標(biāo)。比如,
等同于下面的寫法。
(2)$< $< 指代第一個(gè)前置條件。比如,規(guī)則為 t: p1 p2,那么$< 就指代p1。
等同于下面的寫法。
(3)$? $? 指代比目標(biāo)更新的所有前置條件,之間以空格分隔。比如,規(guī)則為 t: p1 p2,其中 p2 的時(shí)間戳比 t 新,$?就指代p2。 (4)$^ $^ 指代所有前置條件,之間以空格分隔。比如,規(guī)則為 t: p1 p2,那么 $^ 就指代 p1 p2 。 (5)$* $* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。 (6)$(@D) 和 $(@F) $(@D) 和 $(@F) 分別指向 $@ 的目錄名和文件名。比如,$@是 src/input.c,那么$(@D) 的值為 src ,$(@F) 的值為 input.c。 (7)$(<D) 和 $(<F) $(<D) 和 $(<F) 分別指向 $< 的目錄名和文件名。 所有的自動(dòng)變量清單,請(qǐng)看手冊(cè)。下面是自動(dòng)變量的一個(gè)例子。
上面代碼將 src 目錄下的 txt 文件,拷貝到 dest 目錄下。首先判斷 dest 目錄是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), $@ 指代目標(biāo)文件(dest/%.txt)。 3.8 判斷和循環(huán)Makefile使用 Bash 語(yǔ)法,完成判斷和循環(huán)。
上面代碼判斷當(dāng)前編譯器是否 gcc ,然后指定不同的庫(kù)文件。
上面代碼的運(yùn)行結(jié)果。
3.9 函數(shù)Makefile 還可以使用函數(shù),格式如下。
Makefile提供了許多內(nèi)置函數(shù),可供調(diào)用。下面是幾個(gè)常用的內(nèi)置函數(shù)。 (1)shell 函數(shù) shell 函數(shù)用來執(zhí)行 shell 命令
(2)wildcard 函數(shù) wildcard 函數(shù)用來在 Makefile 中,替換 Bash 的通配符。
(3)subst 函數(shù) subst 函數(shù)用來文本替換,格式如下。
下面的例子將字符串"feet on the street"替換成"fEEt on the strEEt"。
下面是一個(gè)稍微復(fù)雜的例子。
(4)patsubst函數(shù) patsubst 函數(shù)用于模式匹配的替換,格式如下。
下面的例子將文件名"x.c.c bar.c",替換成"x.c.o bar.o"。
(5)替換后綴名 替換后綴名函數(shù)的寫法是:變量名 + 冒號(hào) + 后綴名替換規(guī)則。它實(shí)際上patsubst函數(shù)的一種簡(jiǎn)寫形式。
上面代碼的意思是,將變量OUTPUT中的后綴名 .js 全部替換成 .min.js 。 四、Makefile 的實(shí)例(1)執(zhí)行多個(gè)目標(biāo)
上面代碼可以調(diào)用不同目標(biāo),刪除不同后綴名的文件,也可以調(diào)用一個(gè)目標(biāo)(cleanall),刪除所有指定類型的文件。 (2)編譯C語(yǔ)言項(xiàng)目
今天,Make命令的介紹就到這里。下一篇文章我會(huì)介紹,如何用 Make 來構(gòu)建 Node.js 項(xiàng)目。 (完) |
|
|