2.3 make工具及makefile文件
無論是在Linux還是在UNIX環境中,make都是一個非常重要的編譯工具。無論是自己進行項目開發還是安裝應用軟件,都需要使用make工具。利用make工具,可以將大型的開發項目分解成爲多個更易於管理的模塊,對於一個包括幾百個源文件的應用程序而言,使用make工具和makefile文件就可以清晰地理順各個源文件之間的關係。而且如此多的源文件,如果每次都要輸入gcc命令進行編譯的話,對程序員來說是很難忍受的。make工具可以自動完成編譯工作,並且只對程序員在上次編譯後修改過的部分進行編譯。因此,有效地利用make工具可以大大提高項目開發的效率。
2.3.1 make工具簡介
1.make工作原理
make工具最基本的功能是調用makefile文件,通過makefile文件來描述源程序之間的相互依賴關係並自動維護編譯工作。當然,makefile 文件需要按照某種語法進行編寫,需要說明如何編譯各個源文件並連接生成可執行文件,以及定義源文件之間的依賴關係。makefile 文件是許多編譯器(包括Windows下的編譯器)維護編譯信息的常用方法,在集成開發環境中,用戶可以通過友好的界面修改 makefile 文件。
例如,在當前目標下有一個文件名爲"makefile"的文件,其內容如下(此文件的語法結構將在接下來的內容中介紹):
#It is a example for describing makefile |
這個描述文檔就是一個簡單的makefile文件。在這個例子中,第一個字符爲#的行爲註釋行。第一個非註釋行指定edit由目標文件main.o、kbd.o、command.o、display.o、insert.o、search.o、files.o、utils.o連接生成,這只是說明一個依賴關係。第三行描述瞭如何從edit所依賴的文件建立可執行文件,即執行命令cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o,即調用gcc編譯以上.o文件生成edit可執行文件。後面偶數行分別指定各目標文件所依賴的.c和.h文件。而緊跟依賴關係奇數行則指定了如何從目標所依賴的文件建立目標。
在默認的方式下,在當前目錄提示符下輸入"make"命令,系統將自動完成以下操作。
(1)make工具會在當前目錄下尋找名字爲"Makefile"或"makefile"的文件,GNU Make 工具在當前工作目錄中按照GNUmakefile、makefile、Makefile的順序搜索 makefile執行文件。
(2)如果找到,它會查找文件中的第一個目標文件,在上面的例子中,系統將查找到"edit"這個目標,並把這個文件作爲最終的目標文件。
(3)如果edit文件不存在,或是edit所依賴的後面的 .o 文件的修改時間要比edit文件晚,那麼,系統就會執行後面所定義的命令來生成edit這個文件。
(4)如果edit所依賴的.o文件也不存在,那麼make工具會在當前文件中查找目標爲.o文件的依賴性,如果找到,則根據這一個規則生成.o文件。
(5)如果此文件中列出的*.C文件和*.H文件都存在,於是make工具會首先生成 .o 文件,然後再用 .o文件生成可執行文件edit。
這就是make文件的依賴性,make會一層一層地去找文件的依賴關係,直到最終編譯出第一個目標文件。在查找的過程中,如果出現錯誤,比如最後被依賴的文件沒有找到,make就會直接退出並報錯。而對於所定義的命令的錯誤,make不會檢查。
通常,makefile文件中還定義有clean目標,這是一個僞目標(後續小節將詳細介紹),可用來清除編譯過程中生成的中間文件,例如清除上例中的內容:
clean : |
更爲簡單的方法爲:
clean: |
在上述makefile文件中,clean沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行。不過,可以在命令中要求執行,即使用命令"make clean",以清除所有的目標文件,以便重新編譯。
2.make命令
make命令可帶4個可選參數,分別爲標誌、宏定義、描述文件名和目標文件名。其標準形式爲:
make [flags] [macro] [ definitions] [targets] |
(1)flags選項及其含義如下。
-f file:指定file文件爲描述文件,如果file參數爲"-"符,則描述文件指向標準輸入。如果沒有"-f"參數,系統將默認當前目錄下名爲makefile或者名爲Makefile的文件爲描述文件。如果要用其他文件作爲makefile文件,則可用以下make命令選項來指定。
$ make -f filename |
-i:忽略命令執行返回的出錯信息。
-s:沉默模式,在執行之前不輸出相應的命令行信息。
-r:禁止使用build-in規則。
-n:非執行模式,輸出所有執行命令,但並不執行。
-t:更新目標文件。
-q:make操作將根據目標文件是否已經更新返回"0"或非"0"的狀態信息。
-p:輸出所有宏定義和目標文件描述。
-d:Debug模式,輸出有關文件和檢測時間的詳細信息。
-c di:在讀取 makefile 之前改變到指定的目錄dir。
-I dir:包含其他 makefile文件時,利用該選項指定搜索目錄。
-h:help文擋,顯示所有的make選項。
-w:在處理 makefile之前和之後,都顯示工作目錄。
(2)宏定義有兩種方式。一種是在makefile文件中定義宏,另一種是使用make命令時直接在命令行下輸入宏定義(此時的宏如果與makefile文件中的宏同名,它將替代makefile文件中的宏)。
在makefile文件中引用宏時只需在變量前加$符號,如果變量名的長度超過一個字符,在引用時就必須加圓括號"()"。下面都是有效的宏引用:
$(CFLAGS) |
其中後兩個引用是一樣的。以下是宏定義變量的實例。
# Define a macro for the object files |
如果執行不帶參數的make命令,將調用GCC連接3個目標文件和庫文件LS生成可執行文件prog;如果在make命令後帶有新的宏定義:
make "LIBES= -LL -LS" |
則命令行後面的宏定義將覆蓋makefile文件中的宏定義。此時若LL也是庫文件,make命令將連接3個目標文件以及LS和LL兩個庫文件。
(3)target參數用來指定make命令要編譯的目標文件,並且允許同時定義編譯多個。操作時將按照從左到右的順序依次編譯各目標文件。如果命令行中沒有指定目標文件,則系統默認將target指向描述文件中的第一個目標文件。
一般makefile文件有幾個預設的目標可供使用,對應有以下幾個命令。
make all:編譯所有的目標。或只輸入make,此時會編譯源碼,然後連接,並且產生可執行文件。
make clean:清除之前所編譯的可執行文件及目的文件 (object file, *.o)。
make distclean:除了清除可執行文件和目的文件外,同時把Makefile文件也清除掉。
make install:將程序安裝至系統中。如果源代碼編譯無誤,且執行結果正確,便可以把程序安裝至系統預設的執行文件存放路徑。如果用bin_PROGRAMS 巨集,程序會被安裝致電目錄 /usr/local/bin。
make dist:將程式和相關的文件包裝成一個壓縮文件以供發佈。執行完後在當前目錄下會產生一個以 PACKAGEVERSION.tar.gz 爲名稱的文件。
2.3.2 makefile常用規則
makefile文件中主要包含了5項內容:顯式規則、隱晦規則、宏定義、文件指示和註釋。
(1)顯式規則。顯式規則說明如何生成一個或多個目標文件。這由makefile的書寫者明確指出,包括要生成的文件,生成目標文件需要的依賴文件,生成目標文件的命令。例如:
foo.o : foo.c defs.h # foo模塊 |
foo.o是目標,foo.c和defs.h是目標所依賴的源文件,命令爲"cc -c -g foo.c"(此行一定要以Tab鍵開頭)。這個規則包括以下兩個主要內容:
文件的依賴關係:foo.o依賴於foo.c和defs.h文件,如果foo.c和defs.h的文件日期要比foo.o新,或是foo.o不存在,那麼依賴關係發生。
cc命令說明了如何生成foo.o文件。
(2)隱晦規則。由於make有自動推導的功能,它會根據依賴關係決定源文件是否要重新編譯以及源文件之間的編譯順序,所以隱晦規則可以讓程序員簡單方便地書寫makefile文件。
(3)宏(變量)的定義。在makefile中需要定義一系列的宏(變量),宏(變量)一般都是字符串,類似於C語言中的宏。當makefile被執行時,其中的變量都會被擴展到相應的引用位置上,這一內容在前一小節已經介紹。
(4)文件指示。包括3個部分:一個makefile文件中引用另一個Makefile文件,就像C語言中的include一樣;指根據某些情況指定makefile中的有效部分,就像C語言中的預編譯#if一樣;定義多行的命令。
(5)註釋。makefile中只有行註釋,和UNIX的Shell腳本一樣,其註釋用"#"字符開始,類似於C/C++中的"//"。如果要在makefile中使用"#"字符,需用反斜槓進行轉義,如:"\#"。
在書寫各部分內容時,可以使用以下方式。
1.使用通配符
make支持3種通配符:"*"、"?"和"[ ]"。這和B-Shell是相同的。波浪號("~")字符在文件名中也有特殊的用途,例如"~/test",這就表示當前用戶的$HOME目錄下的test目錄。
通配符用於代替一系列內容,如"*.c"表示所有後綴爲.c的文件。需要注意的是,如果文件名中有通配符,如"*",則需使用轉義字符"\",如"\*"來表示真實的"*"字符。
通配符可用在顯示規則中,如:
print: *.c |
目標print依賴於所有的.c文件。
通配符同樣可用在變量中,如:
objects = *.o |
objects的值爲"*.o"。
2.使用文件搜尋
在大的工程中,會有大量的源文件,通常將這些源文件分類存放在不同的目錄中。所以,當make需要去尋找文件的依賴關係時,可以在文件前加上路徑,但最好的方法是把路徑告訴make,讓make去自動查找。makefile文件中的特殊變量"VPATH"就是用於完成這個功能的,如果沒有指明這個變量,make只會在當前的目錄中去查找依賴文件和目標文件。如果定義了這個變量,那麼,make在當前目錄找不到相關文件的情況下,會自動到所指定的目錄中查找。
VPATH = src:../headers |
這句代碼指定了兩個目錄"src"和"../headers",make會按照這個順序進行搜索。目錄間用"冒號"分隔。
另一個設置文件搜索路徑的方法是使用make命令的"vpath"關鍵字(注意,是小寫的),這和VPATH變量很類似,但是它更靈活。它可以指定在不同的目錄中搜索不同的文件。這是一個很靈活的功能。它的使用方法有3種:
vpath<pattern><directories>:爲符合模式pattern的文件指定搜索目錄directories。
vpath <pattern>:清除符合模式<pattern>的文件搜索目錄。
vpath:清除所有已設置好了的文件搜索目錄。
<pattern>指定了要搜索的文件集,<pattern>需要包含"%"字符。"%"表示的意思是匹配一個或若干個字符,例如:
"%.h |
表示所有以".h"結尾的文件。
<directories>則指定了<pattern>文件集的搜索目錄。例如:
vpath %.h ../headers |
表示要求make命令在"../headers"目錄下搜索所有以".h"結尾的文件(如果文件在當前目錄沒有找到)。
程序員可以連續地使用vpath語句,以指定不同的搜索策略。如果連續的vpath語句中出現了相同的<pattern>,或是重複了的<pattern>,那麼,make會按照vpath語句的先後順序來執行搜索。如:
vpath %.c foo |
表示以".c"結尾的文件,先被在"foo"文件夾下搜索,然後被在"blish"文件夾下搜索,最後被在"bar"文件夾下搜索。
3.僞目標說明
前面內容中提到過clean僞目標,語法如下:
clean: |
clean並不是一個文件,而只是一個僞目標,由於"僞目標"不是文件,所以make無法生成它的依賴關係和決定它是否要執行。因此,只有通過顯示地指明這個"目標"才能讓其生效這要求僞目標不能和文件重名。爲了避免因和文件重名而使其無效,可以使用一個特殊的標記".PHONY"來顯示地指明一個目標是"僞目標"。即向make命令說明,不管是否有這個文件,這個目標就是"僞目標"。語法如下:
.PHONY: clean |
如果執行make clean命令,將會執行rm *.o temp命令。
一般情況下,僞目標沒有依賴的文件,但是,也可以爲僞目標指定所依賴的文件。僞目標同樣可以作爲"默認目標",只要將其放在第一個即可。例如,Makefile文件需要生成若干個可執行文件,但用戶又只想簡單地敲一個make命令,並且,所有的目標文件都寫在一個makefile文件中,那麼此時可以使用"僞目標"這個特性。
all: prog1 prog2 prog3 |
".PHONY : all"聲明瞭"all"這個目標爲"僞目標"。makefile中的第一個目標會被作爲其默認目標。這裏聲明瞭"all"的僞目標,它依賴於其他3個目標。由於僞目標總是會被執行,所以其依賴的那3個目標就總比"all"新。所以,其他3個目標的規則總是會被執行。
參考資料:
【1】linux高級程序設計[書籍]