|
本文是介紹3.3 初識(shí) Makefile+3.4Makefile語法 3.3 初識(shí) Makefile 3.3.1 什么是 Makefile 在 3.2 章節(jié)我們了解了在 Ubuntu 系統(tǒng)下通過 gcc 編譯器來編譯 C 程序,在我們演示的歷程中只有一個(gè)C 文件,我們直接在終端輸入 gcc 的編譯命令,就完成了 C 程序的編譯。我們?cè)趯?shí)際開發(fā)過程中,如果我們的工程有幾十個(gè),或者幾百幾千個(gè) C 文件,我們通過在終端輸入 gcc 命令來編譯,這顯然是不現(xiàn)實(shí)的。為了解決這個(gè)問題我們可以使用“make”命令,它會(huì)解析 Makefile 文件中的指令(應(yīng)該說是規(guī)則)來編譯整個(gè)工程。在 Makefile 文件中描述了整個(gè)工程的所有文件的編譯順序,編譯規(guī)則。 作為一個(gè)專業(yè)的程序員,一定要掌握 Makefile 的,我們可以通過 Makefile 能了解到整個(gè)工程的處理過程的。 由于Makefile涉及到很多的知識(shí)點(diǎn),以至于可以單獨(dú)寫本書來講述,所以本章我們只是講解下Makefile的基礎(chǔ)入門,如果詳細(xì)的研究 Makefile,可以給大家推薦《跟我一起寫 Makefile》這個(gè)電子文檔,該文檔已經(jīng)放在了:i.MX6UL 終結(jié)者光盤資料\09_其它參考資料里面了。 3.3.2 第一個(gè) Makefile在本節(jié)我們建立這樣一個(gè)工程,計(jì)算兩個(gè)整形數(shù)的和,并將結(jié)果在終端顯示出來。在這個(gè)工程中一共有main.c、calc.c 兩個(gè) C 文件和 calc.h 這個(gè)頭文件。其中 main.c 是主文件,calc.c 負(fù)責(zé)接收 main.c 傳過來的數(shù)據(jù),然后進(jìn)行相加。main.c 文件的內(nèi)用如下: #include include "calc.h" int main(int argc, char *argv[]) { int a = 3, b = 6, sum; sum = calc(a, b); printf("%d + %d = %d\n", a, b, sum); return 0; } calc.c 文件內(nèi)容如下: #include int calc(int a, ing b) { return (a+b); } 文件 calc.h 內(nèi)容如下: #ifndef _CALC_H #define _CALC_H int calc(int a, int b); #endif 上面就是我們這個(gè)工程的所有源文件,我們?cè)诮K端使用 gcc 編譯這個(gè)工程,在終端輸入“gcc main.ccalc.c -o main”,該命令的意思是用 gcc 對(duì) main.c、calc.c 進(jìn)行編譯,然后輸出可執(zhí)行文件 main,運(yùn)行結(jié)果如下圖所示:
通過上圖可以看到生成了可執(zhí)行文件 main,我們?cè)诮K端運(yùn)行 main 執(zhí)行文件,運(yùn)行結(jié)果如下圖所示:
我們可以看到上圖的運(yùn)行結(jié)果和我們?cè)O(shè)計(jì)的結(jié)果是一致的。由于我們的這個(gè)工程是有三個(gè)文件,如果工程有幾百個(gè),幾千個(gè)的文件,或者如果有一個(gè)文件被修改,使用上面的命令將會(huì)編譯所有的文件,如果我們的工程有上萬個(gè)文件,編譯完一次工程所需要的時(shí)間就很可怕。最優(yōu)的方法就是編譯過一次以后,如果后面在編譯,只編譯修改的文件,這樣就會(huì)節(jié)約很多時(shí)間,因此我們修改下編譯方法,命令如下:
gcc -c main.c gcc -c calc.c gcc main.o calc.o -o main 我們?cè)诮K端輸入上面的命令,結(jié)果如下圖所示:
上圖的第一條和第二條命令里面使用了參數(shù)“-c”是把 main.c 和 calc.c 編譯成對(duì)應(yīng)的.o 文件,最后一條命令是把編譯生成的.o 文件鏈接成可執(zhí)行文件 main。假如我們修改了 main.c 這個(gè)文件。只需將 main.c這個(gè)一個(gè)文件重新編譯下,然后在把所有的.o 文件重新鏈接成可執(zhí)行文件,對(duì)應(yīng)的命令如下: gcc -c main.c gcc main.o calc.o -o main 可是這樣還有一個(gè)問題,如果需要修改的文件有很多,這樣工作量也會(huì)不小,所以我們需要一個(gè)工具: 1.如果工程沒有編譯過,就會(huì)把工程中的.c 文件全部編譯并連接成可執(zhí)行文件 2.如果工程中有某些文件修改了,只編譯修改的文件并連接成可執(zhí)行文件 3.如果工程中的頭文件修改了,那么就要編譯所有引用這個(gè)頭文件的.c 文件,并且連 接成可執(zhí)行文件 我們開頭說的 Makefile 就是完成這個(gè)功能的,下面我們?cè)诠こ讨薪⒁粋(gè) Makefile 文件來實(shí)現(xiàn)這樣的功能(注意:文件名字必須為 Makefile,大小寫是區(qū)分的)。我們使用 vim 創(chuàng)建 Makefile 文件(Makefile和我們的 main.c、calc.c 在同一級(jí)目錄下),然后輸入下面的腳本: main:main.o calc.o gcc -o main main.o calc.o main.o:main.c gcc -c main.c calc.o:calc.c gcc -c calc.c clean: rm -rf *.o rm -rf main 上面腳本縮進(jìn)的行需要使用“Tab”鍵縮進(jìn),不要使用空格,這是 Makefile 的語法要求,編寫完成的腳本如下圖所示: 編寫好 Makefile,保存并退出,然后我們?cè)诮K端輸入“make”命令來編譯我們的工程,make 命令會(huì)在當(dāng)前目錄下查找“Makefile”文件,如果存在的話就按照 Makefile 里面的規(guī)則進(jìn)行編譯,如下圖所示:
通過上圖可以看到編譯產(chǎn)生了 main.o、calc.o 和 main 執(zhí)行文件,說明編譯成功了。接下來我們修改下 main.c 這個(gè)文件,如下圖所示:
然后保存并退出,然后在終端輸入“make”再次編譯下工程,如下圖所示:
通過上圖我們可以看到只重新編譯了修改的 main.c,并最終重新鏈接生成可執(zhí)行文件 main,我們?cè)诮K端運(yùn)行可執(zhí)行文件 main,如下圖所示:
3.4 e Makefile 語法 3.4.1 初識(shí) Makefile Makefile 文件是由一些列的規(guī)則組合而成的,格式如下: target(目標(biāo)文件) ...: prerequisites(依賴的文件) ... command(命令) ... ... 比如 3.3.2 中寫的 Makefile 的規(guī)則: main.o:main.c gcc -c main.c 這條規(guī)則的 main.o 是目標(biāo)文件(將要生成的文件),main.c 是依賴的文件(生成 main.o 需要的文件),“gcc -c main.c”是生成 main.o 需要運(yùn)行的命令。e Makefile 中每行的腳本如果有縮進(jìn)的情況,必須使用“ Tab ” 鍵縮進(jìn),切記不能使用空格縮進(jìn)(這是 e Makefile 的語法要求),大家一定要切記!下面我們來分析一下圖 3.3.2 章節(jié)中寫的 Makefile 文件,腳本如下: 1 main:main.o calc.o 2 2 gcc -o main main.o calc.o 3 3 main.o:main.c 4 4 gcc -c main.c 5 5 calc.o:calc.c 6 6 gcc -c calc.c 7 7 8 8 clean: 9 9 rm -rf *.o 10 rm -rf main 從上圖的運(yùn)行結(jié)果可以看到最后的結(jié)果等于 10 了,和我們程序的設(shè)計(jì)結(jié)果是一樣的。 該腳本一共有 4 條規(guī)則,1、2 行是第一條規(guī)則,3、4 行是第二條規(guī)則,5、6 是第三條規(guī)則 8、9、10是第四條規(guī)則。我們?cè)谶\(yùn)行 make 命令的時(shí)候,會(huì)解析當(dāng)前目錄下的這個(gè) Makefile 文件里面的規(guī)則,首先解析第一條規(guī)則,第一條規(guī)則中的目標(biāo)文件是 main,只要完成了該目標(biāo)文件的更新,整個(gè) Makefile 的功能 就完成了。在第一次編譯的時(shí)候,由于目標(biāo)文件 main 不存在,則會(huì)解析第一條規(guī)則,第一條規(guī)則依賴文件main.o、calc.o,make 命令會(huì)檢查當(dāng)前目錄下是否有這兩個(gè).o 文件,經(jīng)過檢查發(fā)現(xiàn)沒有,然后 make 會(huì)在Makefile 中查找分別以 main.o、calc.o 為目標(biāo)的規(guī)則(第二條,第三條規(guī)則)。執(zhí)行第二條規(guī)則依賴的文件是 main.c,make 命令檢查發(fā)現(xiàn)當(dāng)前目錄下有這個(gè)文件,然后執(zhí)行第二條規(guī)則的命令“gcc -c main.c”生成 main.o 文件。然后執(zhí)行第三條規(guī)則,第三條規(guī)則的目標(biāo)文件是 calc.o,依賴的文件是 calc.c,make命令檢查發(fā)現(xiàn)當(dāng)前目錄下存在該文件,然后執(zhí)行第三條規(guī)則的命令“gcc -c calc.c”生成 calc.o 文件,至此第一條規(guī)則依賴的 main.o、calc.o;兩個(gè)文件已經(jīng)生成了,然后運(yùn)行第一條規(guī)則的命令“gcc -o mainmain.o calc.o”生成 main 文件。因?yàn)?make 命令運(yùn)行的時(shí)候會(huì)從 Makefile 的第一條規(guī)則開始解析,然后根據(jù)第一條規(guī)則的依賴文件去遍歷文件中的“對(duì)應(yīng)規(guī)則”,然后在根據(jù)“對(duì)應(yīng)規(guī)則”的依賴文件去遍歷“對(duì)應(yīng)的規(guī)則”,采用這樣遞歸的方式會(huì)遍歷出完成第一條規(guī)則所需要的所有規(guī)則。下面我們來看看第四條規(guī)則的目標(biāo)文件是 clean,我們通過查看發(fā)現(xiàn)該規(guī)則與第一條規(guī)則沒有關(guān)聯(lián),所以我們?cè)谶\(yùn)行 make 命令的時(shí)候,不會(huì)遍歷到該規(guī)則。我們可以在終端輸入“make clean”命令來運(yùn)行第四條規(guī)則,第四條規(guī)則沒有依賴的文件,所以執(zhí)行運(yùn)行命令“rm -rf *.o”和“rm -rf main”,這兩條命令的功能是刪除以.o 問結(jié)尾的所有文件,刪除文件 main,運(yùn)行如下圖所示:
通過上圖可以看到 main.o、mcalc.o 和 main 三個(gè)文件已經(jīng)刪除了。通過該規(guī)則我們可以清除編譯產(chǎn)生的文件,實(shí)現(xiàn)工程的清理。 我們?cè)賮砜偨Y(jié)一下 make 命令的執(zhí)行過程: 1.make 命令會(huì)在當(dāng)前目錄下查找以 Makefile 命名的文件 2.找到 Makefile 文件,就會(huì)按照 Makefile 里面的規(guī)則去編譯,并生成最終文件 3.當(dāng)發(fā)現(xiàn)目標(biāo)文件不存在或者所依賴的文件比目標(biāo)文件新(修改時(shí)間),就會(huì)執(zhí)行規(guī)則對(duì)應(yīng)的命令來更新。我們可以看到 make 是一個(gè)工具,他會(huì)通過 Makefile 文件里面的內(nèi)容來執(zhí)行具體的編譯過程。
3.4.2 Makefile 的變量 在 3.3.2 章節(jié)中的 Makefile 第一條規(guī)則: main:main.o calc.o gcc -o main main.o calc.o 在該規(guī)則中 main.o、calc.o 這兩個(gè)文件我們輸入了兩次,由于我們的額 Makefile 文件內(nèi)容比較少,如果 Makefile 復(fù)雜的情況下,這種重復(fù)的輸入就會(huì)非常占用時(shí)間,而且修改起來也會(huì)很麻煩,為了解決這個(gè)問題,Makefile 可以使用變量。Makefile 的變量是一個(gè)字符串。比如上面的規(guī)則我們聲明一個(gè)變量,叫objects,objs 或者是 OBJ,反正不管是什么,只要能夠表示 main.o、calc.o 就行了,我們修改上面的規(guī)則 1 objects = main.o calc.o 2 2 main ( objects)3 3 gcc -o main $( objects) 我們來分析下修改后的規(guī)則,首先第一行是我們定義了一個(gè)變量 objects,并給賦值“main.o calc.o”,第二行、第三行用到了變量 objects。Makefile 中的變量引用方式是“$(變量名)”,變量 objects 的賦值使用“=”,Makefile 中變量的賦值還可以使用“:=”、“?=”、“+=”,這四種賦值的區(qū)別如下: 1. “= = ” 賦值符 我們先在用戶根目錄的 work 目錄下創(chuàng)建一個(gè) Makefile 腳本,輸入下面的內(nèi)容: 1 ceshi1 = test 2 ceshi2 = $(ceshi1) 3 ceshi1 = temp 4 5 out: 6 @echo ceshi2 (ceshi2)第一行我們定義了變量并賦值“test”,第二行定義了變量 ceshi2 并賦值變量 ceshi1,第三行修改變量ceshi1 的值為“temp”,第五行、第六行是輸出變量 ceshi2 的值。我們?cè)诮K端輸入“make out”命令,如下圖所示:
在上圖可以看到變量 ceshi2 的值是 temp,也就是變量 ceshi1 最后一次的賦值。 2. “ := ” 賦值符 我們修改“=”賦值符中的代碼,第二行的“=”改成“:=”,代碼如下: 1 ceshi1 = test 2 ceshi2 := $(ceshi1) 3 ceshi1 = temp 4 5 out: 6 @echo ceshi2 (ceshi2)我們?cè)诮K端輸入“make out”命令,如下圖所示: file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml10068\wps4.png 我們可以看到上圖的運(yùn)行結(jié)果輸出變量 ceshi2 的值是 test,雖然在第三行我們修改了變量 ceshi1 的 值,通過本實(shí)驗(yàn)我們可以看到“:=”賦值符的功能了。 3. “ ?= ” 賦值符 ceshi ?= test “?=”賦值符的作用是如果前面沒有給變量 ceshi 賦值,那么變量就賦值“test”,如果前面已經(jīng)賦值了,就使用前面的賦值。 4. “ += ” 賦值符 objs = main.o objs += calc.o 上面的腳本最后變量 objs 的值是“main.o calc.o”,“+=”賦值符的功能是實(shí)現(xiàn)變量的追加。 3.4.3 條件判斷 使用條件判斷,可以讓 make 根據(jù)運(yùn)行時(shí)的不同情況選擇不同的執(zhí)行分支。條件表達(dá)式可以是比較變量的值,或是比較變量和常量的值。其語法有下面兩種: 1. <條件比較> [條件為真時(shí)執(zhí)行的腳本] endif 2. <條件比較> [條件為真時(shí)執(zhí)行的腳本] else [條件為假時(shí)執(zhí)行的腳本] endif 條件比較用到的比較關(guān)鍵字有:ifeq、ifneq、ifdef、ifndef。 ifeq 表示如果比較相等,語法如下: ifeq(<參數(shù) 1>, <參數(shù) 2>) ifneq 表示如果不相等,語法如下: ifneq(<參數(shù) 1>, <參數(shù) 2>) ifdef 表示如果定義了變量,語法如下: ifdef <變量名> ifndef 表示如果沒有定義變量,語法如下: ifndef <變量名> 3.4.4 使用函數(shù) 在 Makefile 中可以使用函數(shù)來處理變量,從而讓我們的命令或是規(guī)則更為的靈活和具有智能。make 所支持的函數(shù)也不算很多,不過已經(jīng)足夠我們的操作了。函數(shù)調(diào)用后,函數(shù)的返回值可以當(dāng)做變量來使用。 函數(shù)的調(diào)用很像變量的使用,也是以“$”來標(biāo)識(shí)的,語法如下: $(<函數(shù)名> <參數(shù)集合>) 或者: ${<函數(shù)名> <參數(shù)集合>} 函數(shù)名和參數(shù)集合之間以空格分隔,參數(shù)集合的參數(shù)通過逗號(hào)分隔。函數(shù)調(diào)用以“$”開頭,以圓括號(hào)或花括號(hào)把函數(shù)名和參數(shù)括起。感覺很像一個(gè)變量。函數(shù)中的參數(shù)可以使用變量。為了風(fēng)格的統(tǒng)一,函數(shù)和變量的括號(hào)最好一樣,如使用“$(subst a,b,$(x))”這樣的形式,而不是“$(subst a,b,${x})”的形式。 因?yàn)榻y(tǒng)一會(huì)更清楚,也會(huì)減少一些不必要的麻煩。 接下來我們介紹幾個(gè)常用的函數(shù),其它的函數(shù)可以參考文檔《跟我一起寫 Makefile》。 t 1.subst 函數(shù) $(subst 此函數(shù)的功能是把字串 $(subst ee,EE,feet on the street) 以上腳本實(shí)現(xiàn)把字符串“feet on the street”中的“ee”字符串替換成“EE”字符串,替換后的字符串 為“feet on the strEEt”。 . 2. t patsubst 函數(shù) $(patsubst 此函數(shù)的功能是查找 $(patsubst %.c,%.o,x.c bar.c) 以上腳本實(shí)現(xiàn)把字串“x.c bar.c”符合模式[%.c]的單詞替換成[%.o],返回結(jié)果是“x.o bar.o” 3.strip 函數(shù) $(strip 此函數(shù)的功能是去掉 $(strip a b c ) 以上腳本實(shí)現(xiàn)把字串“a b c ”去掉開頭和結(jié)尾的空格,結(jié)果是“a b c”。 . 4. g findstring 函數(shù) $(findstring 此函數(shù)的功能是在字串 例: $(findstring a,a b c) $(findstring a,b c) 以上腳本,第一個(gè)返回“a”字符串,第二個(gè)返回空字符串。 r 5.dir 函數(shù) $(dir 此函數(shù)的功能是從文件名序列 分。如果沒有反斜杠,那么返回“./”。返回文件名序列 $(dir src/foo.c hacks) 以上腳本運(yùn)行結(jié)果返回“src/”。 . 6. r notdir 函數(shù) $(notdir 此函數(shù)的功能是從文件名序列 的部分,返回文件名序列 $(notdir src/foo.c) 以上腳本返回字符串“foo.c” . 7. h foreach 函數(shù) $(foreach ,
此函數(shù)的功能是把參數(shù)
names := a b c d files := $(foreach n,$(names),$(n).o) 以上腳本實(shí)現(xiàn)$(name)中的單詞會(huì)被挨個(gè)取出,并存到變量“n”中,“$(n).o”每次根據(jù)“$(n)”計(jì)算出一個(gè)值,這些值以空格分隔,最后作為 foreach 函數(shù)的返回,所以$(files)的值是“a.o b.o c.o d.o”。(注意,foreach 中的參數(shù)是一個(gè)臨時(shí)的局部變量,foreach 函數(shù)執(zhí)行完后,參數(shù)的變量將不在作用,其作用域只在 foreach 函數(shù)當(dāng)中)。 3.4.5 在規(guī)則中使用通配符 如果我們想定義一系列比較類似的文件,我們很自然地就想起使用通配符。make 命令支持三種通配符:“*”,“?”和“[...]”,這是和 Unix 的 B-Shell 是相同的。“~”字符在文件名中也有比較特殊的用途。如果是“~/test”,這就表示當(dāng)前用戶根目錄下的 test 文件。而“~admin/test”則表示用戶 admin 根目錄下的 test 文件。通配符代替了一系列的文件,如“*.c”表示所有后綴為 .c 的文件。一個(gè)需要我們注意的是,如果我們的文件名中有通配符,如:“*”,那么可以用轉(zhuǎn)義字符“\”,如“\*” 來表示真實(shí)的“*”字符,而不是任意長(zhǎng)度的字符串。 下面我們來看幾個(gè)具體的示例: clean: rm -rf *.o 上面這個(gè)示例說明通配符可以在規(guī)則的命令中使用。 print: *.c 上面這個(gè)示例說明通配符可以在規(guī)則的依賴中使用 objects = *.o 上面這個(gè)示例表示了,通符同樣可以用在變量中。并不是說[*.o]會(huì)展開,objects 的值就是“*.o”。Makefile中的變量其實(shí)就是 C/C++中的宏。如果你要讓通配符在變量中展開,也就是讓 objects 的值是所有[.o]的文件名的集合,那么,你可以這樣: objects := $(wildcard *.o) 這種用法由關(guān)鍵字“wildcard”指出,關(guān)于 Makefile 的關(guān)鍵字可以參考文檔《跟我一起寫 Makefile》。 關(guān)于 Makefile 的相關(guān)內(nèi)容我們就介紹到這里,本節(jié)只是對(duì) Makefile 做了基本的講解,Mkaefile 還有大量 完結(jié),更多內(nèi)容關(guān)注: https://item.taobao.com/item.htm?spm=a2oq0.12575281.0.0.34851debJbM0Ye&ft=t&id=614020183147&qq-pf-to=pcqq.c2c
|