雖然make命令內置了很多智能機制,但是無法瞭解應該如何建立應用程序的。你必須爲其提供一個文件,告訴它應用程序應該如何構造,這個文件稱爲makefile。
makefile的語法
makefile文件由一組依賴關係和規則構成。每個依賴關係由一個目標(即將要創建的文件)和一組該目標所依賴的源文件組成。而規則描述瞭如何通過這些依賴文件創建目標。一般來說,目標是一個單獨的可執行文件。
make命令會讀取makefile文件的內容,它先確定目標文件或要創建的文件,然後比較該目標所依賴的源文件的日期和時間以決定該採用哪條規則來構造目標。通常在創建最終的目標文件之前,它需要先創建一些中間目標。make命令會根據makefile文件來確定目標文件的創建順序以及正確的規則調用順序。
make命令的選項和參數
make程序本身有許多選項,其中最常用的3個選項如下所示:
-k:它的作用是讓make命令在發現錯誤時仍然繼續執行,而不是在檢測到第一個錯誤時就停下來。你可以利用這個選項在一次操作中發現所有未編譯成功的源文件。
-n:它的作用是讓make命令輸出將要執行的操作步驟,而不真正執行這些操作。
-f<filename>:它的作用是告訴make命令將哪個文件作爲makefile文件。如果未使用這個選項,make命令將首先在當前目錄下查找名爲makefile的文件,如果該文件不存在,它就會查找名爲Makefile的文件。但如果你是在Linux系統中,你使用的可能是GNUMake,這個版本的make命令將在搜索makefile文件和Makefile文件之前,首先查找名爲GNUmakefile的文件。習慣上,許多Linux程序員使用文件名Makefile,因爲如果一個目錄下都是以小寫字母爲名稱的文件,則Makefile文件將在目錄的文件列表中第一個出現。
爲了指示make命令創建一個特定的目標(通常是一個可執行文件),你可以把該目標的名字作爲make命令的一個參數。如果不這麼做,make命令將試圖創建列在makefile文件中的第一個目標。許多程序員都會在自己的makefile文件中將第一個目標定義爲all,然後再列出其它從屬目標。這個約定可以明確地告訴make命令,在未指定特定的目標時,默認情況下應該創建哪個目標。建議都堅持使用這一約定。
1依賴關係
依賴關係定義了最終應用程序裏的每個文件與源文件之間的關係。
在makefile文件中,這些規則的寫法是:先寫目標的名稱,然後緊跟着一個冒號,接着是空格或製表符tab,最後是用空格或製表符tab隔開的文件列表(這些文件用於創建目標文件)。
例:
myapp: main.o 2.o 3.o main.o: main.c a.h 2.o: 2.c a.h b.h 3.o: 3.c b.h c.h
它表示目標myapp依賴於main.o2.o和3.o,而main.o依賴於main.c和a.h等等
這組依賴關係形成一個層次結構,它顯示了源文件之間的關係。如果文件b.h發生改變,你就需重新編譯2.o和3.o,而由於2.o和3.o發生了改變,你還需要重新創建目標myapp。
如果想一次創建多個文件,你可以利用僞目標all。假設應用程序由二進制文件myapp和使用手冊myapp.1組成。你可以用下面這行語句進行定義:
all:myappmyapp.1
這裏再次強調,如果未指定一個all目標,則make命令將只創建它在文件makefile中找到的第一個目標。
2規則
makefile文件的第二部分內容是規則,它們定義了目標的創建方式。當make命令確定需要重建2.o時,它具體應該使用哪條命令呢?規則所在行必須以製表符tab開頭,用空格是不行的。如果缺少了製表符tab,make命令就不會正常工作,所以發現這個錯誤很容易。
實驗:一個簡單的makefile文件
myapp: main.o 2.o 3.o gcc -o myapp main.o 2.o 3.o main.o: main.c a.h gcc -c main.c 2.o: 2.c a.h b.h gcc -c 2.c 3.o: 3.c b.h c.h gcc-c 3.c /*main.c*/ #include<stdlib.h> #include"a.h" externvoidfunction_two(); externvoidfunction_three(); int main() { function_two(); function_three(); exit(1); } /*2.c*/ #include"a.h" #include"b.h" void function_two() {} /*3.c*/ #include"b.h" #include"c.h" voidfunction_three() {}
makefile文件中的註釋
makefile文件中的註釋以#號開頭,一直延續到這一行的結束。makefile文件中的註釋可以幫助程序的編寫者及其他人理解最初編寫這個文件的目的。
makefile文件中的宏
通過語句MACRONAME=value在makefile文件中定義宏,引用宏的方法是使用$(MACRONAME)或${MACRONAME}。如果想把一個宏的值設置爲空,你可以令等號後面留空。
makefile文件的另一個問題是,它假設編譯器的名字是gcc,而在其他UNIX系統中,編譯器的名字可能是cc。如果想將makefile文件移植到另一版本的UNIX系統中,或在現有系統中使用另一個編譯器,爲了使其工作,你將不得不修改makefile文件中許多行的內容。通過宏定義,你可以方便地修改這些內容。
實驗:帶宏定義的makefile文件
all:myapp #whichcompiler CC=gcc #whereareincludefileskept INCLUDE=. #optionsfordevelopment CFLAGS=-g -Wall -ansi #optionsforrelease #CFLAGS=-o -Wall -ansi myapp: main.o 2.o 3.o $(CC) -o myapp main.o 2.o 3.o main.o: main.c a.h $(CC) -I$(INCLUDE) $(CFLAGS) -c main.c 2.o: 2.c a.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c 3.o: 3.c c.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c 在makefile文件中,有兩個有用的特殊字符,它們出現在命令之前。 -:告訴make命令忽略所有的錯誤。例如:你想創建一個目錄,但又想忽略任何錯誤(比如目錄已存在),你就可以在mkdir命令的前面加上一個減號。 @:告訴make在執行某條命令前不要將該命令顯示在標準輸出上。 實驗:多個目標 all: myapp #whichcompiler CC=gcc #whereareincludefileskept INCLUDE=. #optionsfordevelopment CFLAGS=-g -Wall -ansi #optionsforrelease #CFLAGS=-o -Wall -ansi myapp: main.o 2.o 3.o $(CC) -o myapp main.o 2.o 3.o main.o: main.c a.h $(CC) -I$(INCLUDE) $(CFLAGS) -c main.c 2.o: 2.c a.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c 3.o: 3.c c.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c clean: -rm main.o 2.o 3.o install: myapp @if [ -d ${INSTDIR} ]; \ then \ cp myapp ${INSTDIR} && \ chmod a+x ${INSTDIR}/myapp && \ chmod og-w ${INSTDIR}/myapp && \ echo "Installed in ${INSTDIR}"; \ else \ echo "Sorry,${INSTDIR}does not exist";\ fi
由於make命令在執行規則時會調用一個shell,並且會針對每個規則使用一個新shell,所以必須在上面每行代碼的結尾加上一個反斜槓\,讓所有shell腳本命令在邏輯上處於同一行,並作爲一個整體傳遞給一個shell執行。