Makefile 基礎 - 讓我歡喜讓我憂

你這樣,一個女人,讓我歡喜讓我憂... --- 周華健

 

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,也就這樣了。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章