你這樣,一個女人,讓我歡喜讓我憂... --- 周華健
Makefile 就是一個讓人歡喜讓人憂的東西,
歡喜呢?使用起來超方便。
憂呢?就是語法比較麻煩,一大堆一大堆的,有時候會繁雜。
當然,瞭解它之後,或許,憂,會少一點吧。
比如,
“我沒事”時,你應該說“我錯了”,而不是去玩遊戲。
“肚子疼”時,你應該幫她揉揉肚子,而不是“多喝開水”。
這一篇呢,將帶你進入一個讓你歡喜讓你憂的世界-----Makefile。
1、爲什麼要用 Makefile?
2、Makefile的編寫規則。
3、Makefile常用的函數。
1、爲什麼要用 Makefile?
很久很久以前,人們不用 Makefile,生產一個程序,就只能手動敲命令。
一開始,程序很小很小,只有一個文件,只需要在shell裏敲上:
gcc main.c -o main
後來,程序越來越大,有非常多的文件,需要敲入這個:
gcc main.c 1.c 2.c 3.c ... ... ... -o main
每次修改某個文件,都需要重新編譯所有文件,要好久好久呀,編譯效率非常低下。
有沒有辦法讓提高效率呢?
gcc -c main.c 1.c 2.c 3.c ... ... ... 分別生成 所有的.o文件;
gcc main.o 1.o 2.o 3.o ... ... ... -o main 再把生成的.o文件鏈接成目標文件main;
如果修改 1.c,只需要 先
gcc -c 1.c 生成 1.o 文件;
gcc main.o 1.o 2.o 3.o ... ... ... -o main 再把剛生成的 1.o,和其他之前生成的.o文件,一起鏈接成目標文件main
改哪個編哪個,編譯效率倒是提高了,不過,敲命令,也夠受了,如果改很多文件的話,還要記得,改了哪個,如果編譯比較大型的工程,光是敲命令,都得敲到手抽筋,於是,make應運而生。
make機制:make的機制類似於“改哪個編哪個”,make 會自動檢查源文件.c和對應的文件.o的最後修改時間,如果某個源文件.c的最後修改時間,比對應的文件.o要新,說明,這個文件修改過,需要重新編譯,make就會自動編譯對應的文件,再把新編譯生成的新的對應文件.o,和之前已生成的.o文件,一起鏈接成目標文件xxxx。
Ex: 如果 1.c 被更改,make 會自動檢查 .c 的修改時間 和 .o 的修改時間,發現,1.c 比 1.o 要新,此時,便自動編譯 1.c,再把 1.o 2.o 3.o ... ... ... 一起鏈接成 目標文件 main
那麼,make 怎麼知道我要編譯哪些文件呢?是隻編譯1.c 2.c?還是編譯全部?或者其它的呢?
這個,就看 Makefile 了,make是個大廚,而 Makefile 就是菜譜,菜譜裏寫着,菜名,原材料,加工方法等。
2、Makefile的編寫規則。
上面說到,make是大廚,Makefile是菜譜,我們就來看一看這菜譜吧。
菜名:原材料
<Tab>加工方法
main: main.o
gcc main.o -o main #gcc前面一定要 <Tab>,一定要,一定要,重要事情說三遍!
main.o: main.c
gcc -c main.c
testString:
echo "shuang!shuang!shuang!"
clean:
rm -rf *.o main
main是我們最終想得到的菜,菜譜如下:
main: main.o
gcc main.o -o main
main,就是菜名,
main.o 就是原材料,
gcc main.o -o main 就是加工方式。
試一下,開始做菜 make
效果同直接在命令行輸入:
gcc -c main.c
gcc main.o -o main
我們的加工方式是 gcc main.o -o main,爲什麼還會有一條 gcc -c main.c呢?
因爲我們的原材料,main.o,也是一道菜,是由 gcc -c main.c 加工而成,請看下一條。
上一道菜中的 main.o 這個原材料,它是另一道叫main.o的菜,菜譜如下:
main.o: main.c
gcc -c main.c
main.o,就是菜名,
main.c,就是原材料,
gcc -c main.c,就是加工方式
效果同直接在命令行輸入:gcc -c main.c
testString 和 clean 這道菜呢,是試菜和吃光菜,不需要原材料:
testString:
echo "shuang!shuang!shuang!"
意思是試菜時,要喊 爽!爽!爽!
效果同直接在命令行輸入:echo "shuang!shuang!shuang!"
clean:
rm -rf *.o main
清理 所有的.o文件 和 main文件
效果同直接在命令行輸入:rm -rf *.o main
直接 make 呢?
效果同 make main
總結:
a、當我們執行 make xxx(菜名)時,make會自動執行 xxx(菜名)下的命令(加工方式);
b、當我們執行 make 時,make 會自動執行第一條 xxx:(菜譜上第一個菜名)所對應的命令(加工方式);
c、加工方式前面的<Tab>,必須是退格鍵,不能是空格或者其它;
3、Makefile變量的聲明和賦值。
PS:Makefile裏所有變量都是字符串
變量的聲明,方法有:=、?=、:=、+=、define endef
變量的使用,方法有:$(var)
var="i am a rich man!"
var?="i am a rich man!"
var:="i am a rich man!"
var+="Good!"
define var
"i am a rich man!"
endef
=、?=、define:延時變量,在使用的時候才確定值
:=:立即變量,在定義的時候就確定值
+=:看前面的,前面的是延時就是延時,前面的是立即就是立即
var1=abc
var2=$(var1)def
var1=ghi
echo $(var2)
輸出 ghidef
=、?=、define是延時變量,所以,在 echo $(var2) 時,才確定 var2的值是 $(var1)def,var1的值是 ghi
var1=abc
var2:=$(var1)def
var1=ghi
echo $(var2)
輸出 abcdef
:= 是立即變量,所以,在var2:=$(var1)def 時,就已經確定了var2的值是 abcdef
?=只有在變量第一次賦值的時候有效,也就是說,如果前面變量沒被賦值,就賦值,如果有賦值,則不執行賦值,跳過。
var1?=abc 或者 var1=abc 或者 var1:=abc
var1?=def
echo $(var1)
輸出 abc
var1?=def
echo $(var1)
輸出 def
+=字符串連接,在原有的字符串上加上後續內容,不過中間會有空格喔,呵呵噠
var1=abc
var1+=def
echo $(var1)
輸出 abc def
4、Makefile常用的函數。
字符串替換和分析函數:
$(subst from,to,text)
在text中使用to替換from.
echo $(subst ee,EE,feeet on the street!) 輸出 fEEet on the strEEt!
$(patsubst pattern,repace,text)
在text中尋找符合pattern格式的字,用repace代替它。
$(patsubst %.c,%.o,aa.c.c bb.c) 輸出 aa.c.o bb.o
$(strip string) 去掉前導和結尾,並把中間多個空格壓縮成一個
$(strip I am a rich man!! ) 輸出 I am a rich man!!
$(findstring find,string) 在string中尋找find,有則返回find,無則返回空
$(findstring rich man,I am a rich man!!) 輸出 rich man
$(filter pat...,text) 返回在text中“匹配用空格隔開的pat...”的字,去除不匹配的字。
$(filter %h %n,I am a rich-man) 輸出 rich-man
$(filter-out pat...,text) 返回在text中“匹配用空格隔開的pat...”的字,去除不匹配的字。
$(filter-out %h %n,I am a rich-man) 輸出 I am a
$(sort list...) 去除list...中用空格隔開的重複的單詞,並按字母排序,先符號,後數字,再字母,大小寫區分
$(sort 1. I am a rich man !man Man a rich) 輸出 !man 1. I Man a am man rich
文件名函數:
$(dir dirs...) 抽取每一部分的目錄部分,目錄從文件名的首字符起到最後一個/結束的字符
$(dir /mnt/src.c hack/ddr/aab.a kko.c) 輸出/mnt/ hack/ddr/ ./
/mnt/src.c 的目錄是 /mnt/
hack/ddr/aab.a 的目錄是 hack/ddr/
kko.c 的目錄是 ./
$(notdir names...) 抽取文件名
$(notdir /mnt/src.c hack/ddr/aab.a kko.c) 輸出src.c aab.a kko.c
$(suffix names...) 抽取文件名後綴
$(suffix /mnt/src.c hack/ddr/aab.a kko.d rich-man) 輸出.c .a .d
$(basename names...) 抽取除後綴外的其他字符
$(basename /mnt/src.c hack/ddr/aab.a kko.d rich-man) 輸出/mnt/src hack/ddr/aab kko rich-man
$(addsuffix suffix,name...) 加文件名後綴
$(addsuffix .c,1413 1314 rich-man) 輸出1413.c 1314.c rich-man.c
$(addprefix prefix,names...) 加前綴,比如 src/, hack.c bb.c 輸出 src/hack.c
$(addprefix rich-,man womem boy girl) 輸出 rich-man rich-womem rich-boy rich-girl
$(wildcard *.c) 返回當前文件夾下.c的文件,輸出 1.c 2.c main.c
其他函數:
$(foreach var,list,text) 將list和var擴展,再將每個list賦給var,text引用var再進行擴展。
dirs:=a b c d e
files:=$(foreach tmp,$(dirs),$(tmp).txt)
echo $(files)
輸出:a.txt b.txt c.txt d.txt e.txt
dir第1次擴展=a, tmp=a
dir第2次擴展=b, tmp=b
…………
最後tmp.txt就是 a.txt b.txt c.txt d.txt e.txt
$(if condition,then,else)
先把condition展開,如果非空,就執行then,如果空,就執行else。
file:=a b c d
$(if $(file),havefile,nothavefile) 返回havefile
#file:=
$(if $(file),havefile,nothavefile) 返回nothavefile
5、其它:
$@:菜名
$^:所有原材料,用空格隔開
$<:第一個原材料
main: main.c 1.c 2.c 3.c
gcc -o main main.c 1.c 2.c 3.c
可以寫成:
gcc -o $@ $^
最後,我們在上面例子的 Makefile 文件上,增加 1.c 2.c 3.c,再運用變量,函數,把它改成一個比較通用的Makefile。
src:=$(shell ls *.c) #列出所有.c 文件
ofile:=$(patsubst %c,%o,$(src)) #在列出的.c文件中尋找符合%c格式的字,用repace代替它。
#經過替代,$(ofile)=main.o 1.o 2.o 3.o
main: $(ofile) #菜名main,原材料 main.o 1.o 2.o 3.o
gcc $^ -o $@ # $^,所有原材料,$@,菜名。加工方式:把所有原材料 gcc 成菜名main
#等同於 gcc main.o 1.o 2.o 3.o -o main
%.o: %.c #菜名,%.o的所有菜,原材料,%.o對應所需要的原材料,要分別加工喔。
gcc -c -g -o $@ $< #等同於 gcc -c -g -o 1.o 1.c / gcc -c -g -o 2.o 2.c / gcc -c -g -o 3.o 3.c
#gcc -c -g -o main.o main.c 分別加工成 1.o 2.o 3.o main.o 幾道菜。
testString:
echo "shuang!shuang!shuang!"
clean:
rm -rf *.o main
分別試一下。
PS: 命令前面加 @,表示靜默編譯,也就是不打印命令。
把這句改爲:
testString:
@echo "shuang!shuang!shuang!"
再看下,看出區別了麼?沒有打印 echo "shuang!shuang!shuang"這名了,只打印了 shuang!shuang!shuang!
基本上,這個讓人又愛又恨的小妖精 Makefile,也就這樣了。