怎樣寫Makefile文件(C語言部分)

  本文摘抄自「跟我一塊兒寫Makefile 」,只是原文中我本身感受比較精要的一部分,而且只針對C語言,使用GCC編譯器。 原文請看這裏:http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefilehtml

        寫完以後才發現基本上都是一些比較枯燥的規則,看看1、2、八三個部分就能夠了。看成參考工具吧,何時用到了再來看看。shell

 

1、概述ubuntu

        我所使用的make 版本是 GNU Make 3.81,使用的系統是 Ubuntu 10.10,GCC版本爲 4.4.5。與原文做者使用的Make 版本很類似。函數

 

1.1  關於程序的編譯和連接工具

        對於C語言的編譯,首先要把源文件編譯成中間代碼文件,即.o文件(在Windows下是.obj文件)。這個動做叫作編譯compile)。而後再把大量Object File合成執行文件,這個動做叫作連接link)。更加詳細的內容能夠參考這裏:使用gcc編譯C程序的詳細過程post

        編譯時,編譯器主要檢查語法、函數與變量的聲明是否正確。函數的聲明一般放在頭文件中(頭文件中應該只放聲明,對於函數的具體實現則應該放到單獨的 .c 源文件中),你告訴編譯器頭文件所在的位置,編譯器就會到相應的地方去找函數聲明。只要語法正確,編譯器就會生成中間文件。通常來講一個源文件(.c)對應一個目標文件(.o)。測試

        連接時,主要是連接函數和全局變量,因此,咱們可使用這些中間目標文件(O文件或是OBJ文件)來連接咱們的應用程序,並不須要源文件的存在。在不少時候,因爲中間目標文件太多,而在連接時須要明顯地指出中間目標文件名,這對於編譯很不方便,因此,咱們要給中間目標文件打個包,在Windows下這種包叫「庫文件」(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件優化

       

2、Makefile 介紹ui

2.1   Makefile的規則spa

target ... : prerequisites ...
	command
	...
	...

        target能夠是一個object file(目標文件),也能夠是一個執行文件,還能夠是一個標籤(label)。對於標籤這種特性,在後續的「僞目標」章節中會有敘述。

        prerequisites就是,要生成那個target所須要的文件或是目標。

        command也就是make須要執行的命令。(任意的shell命令)。注意command以前是TAB鍵,並不是空格鍵。

 

2.2   一個試例

        一個工程有3個頭文件,和8個c文件,關係以下圖:

文件關聯

        咱們的makefile應該是下面的這個樣子的:

edit : main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o

main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o

        make命令會找到Makefile文件中第一個target,而且把它當作最終的目標文件。Makefile所指示出來的關係就是咱們在上面的圖中所展現的關係。

 

2.3   Makefile中使用變量

        makefile的變量也就是一個字符串,理解成C語言中的宏可能會更好。

objects = main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o

edit : $(objects)
	cc -o edit $(objects)

 

 

2.4  讓make自動推導

        只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關係中。

objects = main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o
 cc = gcc

edit : $(objects)
	cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
	rm edit $(objects)
「.PHONY」表示,clean是個僞目標文件。

 

2.5   清空目標文件的規則

.PHONY : clean
clean :
	-rm edit $(objects)
在rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續作後面的事。固然,clean的規則不要放在文件的開頭,否則,這就會變成make的默認目標,相信誰也不肯意這樣。不成文的規矩是——「clean歷來都是放在文件的最後」。

 

2.6   Makefile中有什麼?

Makefile裏主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和註釋。

  1. 顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
  2. 隱晦規則。因爲咱們的make有自動推導的功能,因此隱晦的規則可讓咱們比較簡略地書寫Makefile,這是由make所支持的。
  3. 變量的定義。在Makefile中咱們要定義一系列的變量,變量通常都是字符串,這個有點像你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
  4. 文件指示。其包括了三個部分,一個是在一個Makefile中引用另外一個Makefile,就像C語言中的include同樣;另外一個是指根據某些狀況指定Makefile中的有效部分,就像C語言中的預編譯#if同樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。
  5. 註釋。Makefile中只有行註釋,和UNIX的Shell腳本同樣,其註釋是用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile中使用「#」字符,能夠用反斜框進行轉義,如:「\#」。

最後,還值得一提的是,在Makefile中的命令,必需要以[Tab]鍵開始。

 

2.7   引用其餘Makefile

        在include前面能夠有一些空字符,可是毫不能是[Tab]鍵開始。你有這樣幾個Makefile:a.mk、b.mk、c.mk,還有一個文件叫foo.make,以及一個變量$(bar),其包含了 e.mk和f.mk。

include foo.make *.mk $(bar)

若是文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,若是當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找:

  1. 若是make執行時,有「-I」或「--include-dir」參數,那麼make就會在這個參數所指定的目錄下去尋找。
  2. 若是目錄<prefix>;/include(通常是:/usr/local/bin或/usr/include)存在的話,make也會去找。

若是有文件沒有找到的話,make會生成一條警告信息,但不會立刻出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取, make會再重試這些沒有找到,或是不能讀取的文件,若是仍是不行,make纔會出現一條致命信息。若是你想讓make不理那些沒法讀取的文件,而繼續執行,你能夠在include前加一個減號「-」,其表示,不管include過程當中出現什麼錯誤,都不要報錯繼續執行。


2.8   make的工做方式

GNU的make工做時的執行步驟入下:(想來其它的make也是相似)

  1. 讀入全部的Makefile。
  2. 讀入被include的其它Makefile。
  3. 初始化文件中的變量。
  4. 推導隱晦規則,並分析全部規則。
  5. 爲全部的目標文件建立依賴關係鏈。
  6. 根據依賴關係,決定哪些目標要從新生成。
  7. 執行生成命令。

1-5步爲第一個階段,6-7爲第二個階段。第一個階段中,若是定義的變量被使用了,那麼,make會把其展開在使用的位置。但make並不會徹底立刻展開,make使用的是拖延戰術,若是變量出如今依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變量纔會在其內部展開。

 

        看完這些再看 第八部分:隱含規則,就能夠了,中間的都不用看了。我怎麼寫了這麼多。

 

3、書寫規則

3.1   文件搜索

        使用特殊變量VPATH 來搜索,若是不指定,make只會在當前的目錄中去找尋依賴文件和目標文件。指定了這個變量,在當前目錄搜索完而找不到以後會搜索相應目錄。

VPATH = src:../headers

        目錄之間用冒號隔開。

        還可使用vpath關鍵字,它的使用方法有三種:這裏只說一種,詳情看原文

一、vpath <pattern> <directories>

爲符合模式<pattern>的文件指定搜索目錄<directories>。

        vapth使用方法中的<pattern>須要包含「%」字符。「%」的意思是匹配零或若干字符,(需引用「%」,使用「\%")例如,「%.h」表示全部以 「.h」結尾的文件。<pattern>指定了要搜索的文件集,而<directories>則指定了< pattern>的文件集的搜索的目錄。

vpath %.h ../headers

 

3.2   僞目標
        好比剛開始咱們指定的

.PHONY : clean
clean :
	rm *.o temp

        也能夠爲僞目標指定依賴文件,常見於all,而且把它放在最前,指定爲Makefile的終極目標。因爲all是僞目標,因此不會生成all文件。

all : prog1 prog2 prog3
.PHONY : all

 

3.3   靜態模式
        靜態模式能夠更加容易地定義多目標的規則,語法:

<targets ...>: <target-pattern>: <prereq-patterns ...>
	<commands>
	...


        以下面的例子:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

        等價於:

foo.o : foo.c
	$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
	$(CC) -c $(CFLAGS) bar.c -o bar.o


        $(objects): %.o: %.c,這裏先把$(object) 在這裏展開,表示把全部objects之中的.o文件都用.c文件與之對應起來。

        $< 表示依賴文件,這裏即.c文件, $@ 表示target文件,這裏即.o文件。

 

3.4   自動生成依賴性

        在GCC中,咱們用gcc –MM main.c 這條命令就能夠查看全部main.o 的依賴關係。因而,能夠利用這一特性來讓Makefile自動生成依賴關係,不用咱們本身寫了。

        具體看這裏

 

4、書寫命令

 

 

 

 

 

 

 

        每條規則中的命令和操做系統Shell的命令行是一致的。make會按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則後面的分號後的。

4.1   顯示命令

        用「@」字符在命令行前,那麼,這個命令將不被make顯示出來。

        make執行時,帶入make參數「-n」或「--just-print」,那麼其只是顯示命令,但不會執行命令。利於調試Makefile。

        make參數「-s」或「--slient」則是全面禁止命令的顯示。

 

4.2   命令執行

        若是上一條命令執行的結果須要在下一條指令中使用,那麼這兩個指令應該放在一行,中間用分號隔開。

exec:
	cd /home/hchen; pwd

 

4.3   命令出錯

        若是想要忽略命令的錯誤,在它以前加 ‘-’便可。

 

4.4   嵌套執行make

        在一些大的工程中,咱們會把咱們不一樣模塊或是不一樣功能的源文件放在不一樣的目錄中,咱們能夠在每一個目錄中都書寫一個該目錄的Makefile,這有利於讓咱們的Makefile變得更加地簡潔,而不至於把全部的東西所有寫在一個Makefile中,這樣會很難維護咱們的Makefile,這個技術對於咱們模塊編譯和分段編譯有着很是大的好處。

        例如,咱們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明瞭這個目錄下文件的編譯規則。那麼咱們總控的Makefile能夠這樣書寫:

subsystem:
        cd subdir && $(MAKE)

        等價於:

subsystem:
        $(MAKE) -C subdir

        具體看這裏

 

5、使用變量

        在Makefile中的定義的變量,表明了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。變量是大小寫敏感的。「$<」、「$@」等,這些是自動化變量。

5.1   變量的基礎

        變量在定義時要賦初值。使用時在其前加$ 符號,而且用() 或者 {} 把變量名括起來。"$$」 表示真實的 $。

 

5.2   變量中的變量

        這裏說變量怎樣定義,怎樣賦值。

        變量賦值有三種方式,一種是 = ,一種是 := ,一種是 ?= 。前一種可使用延後定義的變量,中間只能按照從上到下的順序定義變量。最後一種表示若是這個變量已經賦值,就再也不對他進行賦值了,若是沒有賦值,就對他賦值。詳情參看這裏

        += 是追加賦值。

        好比定義一個空格,下面的變量space就表示一個空格,其餘方法還很差定義空格:

nullstring :=# there is nothing
space := $(nullstring) # end of the line

 

 

5.3   變量高級用法

      · 變量值的替換:
        咱們能夠替換變量中的共有的部分,其格式是「$(var:a=b)」或是「${var:a=b}」,其意思是,把變量「var」中全部以「a」字串「結尾」的「a」替換成「b」字串。這裏的「結尾」意思是「空格」或是「結束符」。例如:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

        這個示例中,咱們先定義了一個「$(foo)」變量,而第二行的意思是把「$(foo)」中全部以「.o」字串「結尾」所有替換成「.c」,因此咱們的「$(bar)」的值就是「a.c b.c c.c」。

對於靜態模式,咱們要這樣寫:   bar := $(foo:%.o=%.c)

      · 把變量的值再當成變量

        即便用變量時,每一個變量都展開成一個字符串使用:

x = y
y = z
a := $($(x))

        在這個例子中,$(x)的值是「y」,因此$($(x))就是$(y),因而$(a)的值就是「z」。(注意,是「x=y」,而不是「x=$(y)」)

        更加詳細的描述見這裏

 

5.4   目標變量

        爲某個target指定只會在這條規則以及它所連帶的規則中使用的變量:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
        $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
        $(CC) $(CFLAGS) prog.c

foo.o : foo.c
        $(CC) $(CFLAGS) foo.c

bar.o : bar.c
        $(CC) $(CFLAGS) bar.c

        在這個示例中,無論全局的$(CFLAGS)的值是什麼,在prog目標,以及其所引起的全部規則中(prog.o foo.o bar.o的規則),$(CFLAGS)的值都是「-g」

 

6、使用函數

        在Makefile中可使用函數來處理變量,從而讓咱們的命令或是規則更爲的靈活和具備智能。make所支持的函數也不算不少,不過已經足夠咱們的操做了。函數調用後,函數的返回值能夠當作變量來使用。

6.1   函數調用的語法

        函數調用,很像變量的使用,也是以「$」來標識的,其語法以下:

$(<function> <arguments>)
或者
${<function> <arguments>}


        <arguments>爲函數的參數,參數間以逗號「,」分隔,而函數名和參數之間以「空格」分隔。下面是一個具體的例子:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

        在這個示例中,$(comma)的值是一個逗號。$(space)使用了$(empty)定義了一個空格,$(foo)的值是「a b c」,$ (bar)的定義用,調用了函數「subst」,$(bar)的值是「a,b,c」。

 

6.2   字符串處理函數

$(subst <from>,<to>,<text>) 

  • 名稱:字符串替換函數——subst。
  • 功能:把字串<text>中的<from>字符串替換成<to>。
  • 返回:函數返回被替換事後的字符串。

 

$(patsubst <pattern>,<replacement>,<text>) 

  • 名稱:模式字符串替換函數——patsubst。
  • 功能:查找<text>中的單詞(單詞以「空格」、「Tab」或「回車」「換行」分隔)是否符合模式< pattern>,若是匹配的話,則以<replacement>替換。這裏,<pattern>能夠包括通配符 「%」,表示任意長度的字串。若是<replacement>中也包含「%」,那麼,<replacement>中的這個 「%」將是<pattern>中的那個「%」所表明的字串。(能夠用「\」來轉義,以「\%」來表示真實含義的「%」字符)
  • 返回:函數返回被替換事後的字符串。

        這和咱們前面「變量章節」說過的相關知識有點類似。如:

        「$(var:<pattern>=<replacement>;)」 至關於 「$(patsubst <pattern>,<replacement>,$(var))」,

        而「$(var: <suffix>=<replacement>)」 則至關於 「$(patsubst %<suffix>,%<replacement>,$(var))」。

        例若有:objects = foo.o bar.o baz.o, 那麼,「$(objects:.o=.c)」和「$(patsubst %.o,%.c,$(objects))」是同樣的。

 

$(filter <pattern...>,<text>)

  • 名稱:過濾函數——filter。
  • 功能:以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。能夠有多個模式。
  • 返回:返回符合模式<pattern>;的字串。

        其餘更多的函數和示例參看這裏

 

6.3   文件名操做函數

        這裏列出不少對文件名的操做函數。

        還有foreach, if, call這三個比較特殊的函數。

 

7、make的運行

7.1   指定Makefile

        指定某個不叫Makefile的文件運行make命令,加-f選項:

make –f hchen.mk

 

7.2   指定目標

 

      「all」這個僞目標是全部目標的目標,其功能通常是編譯全部的目標。「clean」這個僞目標功能是刪除全部被make建立的文件。「install」這個僞目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。「print」這個僞目標的功能是例出改變過的源文件。「tar」這個僞目標功能是把源程序打包備份。也就是一個tar文件。「dist」這個僞目標功能是建立一個壓縮文件,通常是把tar文件壓成Z文件。或是gz文件。「TAGS」這個僞目標功能是更新全部的目標,以備完整地重編譯使用。「check」和「test」這兩個僞目標通常用來測試makefile的流程。

       

7.3   檢查規則

        「-n」 「--just-print」 「--dry-run」 「--recon」 不執行參數,這些參數只是打印命令,無論目標是否更新,把規則和連帶規則下的命令打印出來,但不執行,這些參數對於咱們調試makefile頗有用處。

 

7.4   make的參數

        點擊小標題。

 

8、隱含規則

8.1   C語言編譯的隱含規則

        「<n>;.o」的目標的依賴目標會自動推導爲「<n>;.c」,而且其生成命令是「$(CC) –c $(CPPFLAGS) $(CFLAGS)」。

        其餘語言的隱含規則看這裏

 

8.2   隱含規則使用的變量

        一、關於命令的變量

 

              CCC語言編譯程序。默認命令是「cc」。

        二、關於命令參數的變量

 

             CFLAGSC語言編譯器參數。

        更多變量看這裏

 

8.3   模式規則

 

 

 

 

 

 

 

 

 

        模式規則中都是用%,它表示匹配0個(仍是1個?)或多個字符。

   %.o : %.c
           $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@


 

8.4   自動化變量

        所謂自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至全部的符合模式的文件都取完了。這種自動化變量只應出如今模式規則的命令中。

        在  a: a.o b.o c.o 這個依賴規則中,a 叫作target,即目標; a.o b.o c.o 都叫作 依賴目標。

 

$@
表示規則中的目標文件集(target,好比上面例子中的%.o)。在模式規則中,若是有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。
$%
僅當 目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標是"foo.a(bar.o)",那麼,"$%"就是 "bar.o","$@"就是"foo.a"。若是目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值爲空。
$< 
依賴目標(好比上例種的%.c)中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
$?
全部比目標新的 依賴目標 的集合。以空格分隔。
$^
全部的 依賴目標 的集合。以空格分隔。若是在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。
$+
這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目標。
$*
這個變量表示目標模式中"%"及其以前的部分。若是目標是"dir/a.foo.b",而且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。

 

 

 

多麼但願有個實例能夠作一下呀,有好多文件,看看怎麼把這些文件用Makefile組織起來,而且優化Makefile的文件。只看這些乾巴巴的規則,一下子就累了………………………………

相關文章
相關標籤/搜索