GNU make 中文手冊(第二章: GNU make 介紹)Linux 2009-05-18 14:31:47 閱讀32 評論0 字號:大中小 訂閱 make在執(zhí)行時,需要一個命名為Makefile的文件。這個文件告訴make以何種方式編譯源代碼和鏈接程序。典型地,可執(zhí)行文件可由一些.o文件按照一定的順序生成或者更新。如果在你的工程中已經(jīng)存在一個活著多個正確的Makefile。當對工程中的若干源文件修改以后,需要根據(jù)修改來更新可執(zhí)行文件或者庫文件,正如前面提到的你只需要在shell下執(zhí)行“make”。make會自動根據(jù)修改情況完成源文件的對應.o文件的更新、庫文件的更新、最終的可執(zhí)行程序的更新。 make通過比較對應文件(規(guī)則的目標和依賴,)的最后修改時間,來決定哪些文件需要更新、那些文件不需要更新。對需要更新的文件make就執(zhí)行數(shù)據(jù)庫中所記錄的相應命令(在make讀取Makefile以后會建立一個編譯過程的描述數(shù)據(jù)庫。此數(shù)據(jù)庫中記錄了所有各個文件之間的相互關系,以及它們的關系描述)來重建它,對于不需要重建的文件make什么也不做。 而且可以通過make的命令行選項來指定需要重新編譯的文件。 Problems and Bugs ================= If you have problems with GNU `make' or think you've found a bug, please report it to the developers; we cannot promise to do anything but we might well want to fix it. Before reporting a bug, make sure you've actually found a real bug.Carefully reread the documentation and see if it really says you can do what you're trying to do. If it's not clear whether you should be able to do something or not, report that too; it's a bug in the documentation! Before reporting a bug or trying to fix it yourself, try to isolate it to the smallest possible makefile that reproduces the problem. Then send us the makefile and the exact results `make' gave you, including any error or warning messages. Please don't paraphrase these messages: it's best to cut and paste them into your report. When generating this small makefile, be sure to not use any non-free or unusual tools in your commands: you can almost always emulate what such a tool would do with simple shell commands. Finally, be sure to explain what you expected to occur; this will help us decide whether the problem was really in the documentation. Once you have a precise problem you can report it in one of two ways.Either send electronic mail to:
bug-make@
or use our Web-based project management tool, at:
http://savannah./projects/make/
In addition to the information above, please be careful to include the version number of `make' you are using. You can get this information with the command `make --version'. Be sure also to include the type of machine and operating system you are using. One way to obtain this information is by looking at the final lines of output from the command `make --help'. 以上時GNU make的bug反饋方式。如果在你使用GNU make過程中。發(fā)現(xiàn)bug或者問題??梢酝ㄟ^以上的方式和渠道反饋。 好了。開始我們的神奇之旅吧!2.1 Makefile簡介在執(zhí)行make之前,需要一個命名為Makefile的特殊文件(本文的后續(xù)將使用Makefile作為這個特殊文件的文件名)來告訴make需要做什么(完成什么任務),該怎么做。通常,make工具主要被用來進行工程編譯和程序鏈接。 本節(jié)將分析一個簡單的Makefile,它對一個包含8個C的源代碼和三個頭文件的工程進行編譯和鏈接。這個Makefile提供給了make必要的信息,make程序根據(jù)Makefile中的規(guī)則描述執(zhí)行相關的命令來完成指定的任務(如:編譯、鏈接和清除編譯過程文件等)。復雜的Makefile我們將會在本文后續(xù)進行討論。 當使用make工具進行編譯時,工程中以下幾種文件在執(zhí)行make時將會被編譯(重新編譯): 1. 所有的源文件沒有被編譯過,則對各個C源文件進行編譯并進行鏈接,生成最后的可執(zhí)行程序; 2. 每一個在上次執(zhí)行make之后修改過的C源代碼文件在本次執(zhí)行make時將會被重新編譯; 3. 頭文件在上一次執(zhí)行make之后被修改。則所有包含此頭文件的C源文件在本次執(zhí)行make時將會被重新編譯。 后兩種情況是make只將修改過的C源文件重新編譯生成.o文件,對于沒有修改的文件不進行任何工作。重新編譯過程中,任何一個源文件的修改將產(chǎn)生新的對應的.o文件,新的.o文件將和以前的已經(jīng)存在、此次沒有重新編譯的.o文件重新連接生成最后的可執(zhí)行程序。 首先讓我們先來看一些Makefile相關的基本知識。2.2 Makefile規(guī)則介紹一個簡單的Makefile描述規(guī)則組成: TARGET... : PREREQUISITES... COMMAND ... ... target:規(guī)則的目標。通常是最后需要生成的文件名或者為了實現(xiàn)這個目的而必需的中間過程文件名??梢允?/span>.o文件、也可以是最后的可執(zhí)行程序的文件名等。另外,目標也可以是一個make執(zhí)行的動作的名稱,如目標“clean”,我們稱這樣的目標是“偽目標”。 prerequisites:規(guī)則的依賴。生成規(guī)則目標所需要的文件名列表。通常一個目標依賴于一個或者多個文件。 command:規(guī)則的命令行。是規(guī)則所要執(zhí)行的動作(任意的shell命令或者是可在shell下執(zhí)行的程序)。它限定了make執(zhí)行這條規(guī)則時所需要的動作。 一個規(guī)則可以有多個命令行,每一條命令占一行。注意:每一個命令行必須以[Tab]字符開始,[Tab]字符告訴make此行是一個命令行。make按照命令完成相應的動作。這也是書寫Makefile中容易產(chǎn)生,而且比較隱蔽的錯誤。 命令就是在任何一個目標的依賴文件發(fā)生變化后重建目標的動作描述。一個目標可以沒有依賴而只有動作(指定的命令)。比如Makefile中的目標“clean”,此目標沒有依賴,只有命令。它所定義的命令用來刪除make過程產(chǎn)生的中間文件(進行清理工作)。 在Makefile中“規(guī)則”就是描述在什么情況下、如何重建規(guī)則的目標文件,通常規(guī)則中包括了目標的依賴關系(目標的依賴文件)和重建目標的命令。make執(zhí)行重建目標的命令,來創(chuàng)建或者重建規(guī)則的目標(此目標文件也可以是觸發(fā)這個規(guī)則的上一個規(guī)則中的依賴文件)。規(guī)則包含了文件之間的依賴關系和更新此規(guī)則目標所需要的命令。 一個Makefile文件中通常還包含了除規(guī)則以外的很多東西(后續(xù)我們會一步一步的展開)。一個最簡單的Makefile可能只包含規(guī)則。規(guī)則在有些Makefile中可能看起來非常復雜,但是無論規(guī)則的書寫是多么的復雜,它都符合規(guī)則的基本格式。 make程序根據(jù)規(guī)則的依賴關系,決定是否執(zhí)行規(guī)則所定義的命令的過程我們稱之為執(zhí)行規(guī)則。 2.3 簡單的示例本小節(jié)開始我們在第一小節(jié)中提到的例子。此例子由3個頭文件和8個C文件組成。我們將書寫一個簡單的Makefile,來描述如何創(chuàng)建最終的可執(zhí)行文件“edit”,此可執(zhí)行文件依賴于8個C源文件和3個頭文件。Makefile文件的內容如下:
#sample 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
首先書寫時,可以將一個較長行使用反斜線(\)來分解為多行,這樣可以使我們的Makefile書寫清晰、容易閱讀理解。但需要注意:反斜線之后不能有空格(這也是大家最容易犯的錯誤,錯誤比較隱蔽)。我們推薦將一個長行分解為使用反斜線連接得多個行的方式。在完成了這個Maekfile以后;需要創(chuàng)建可執(zhí)行程序“edit”,所要做的就是在包含此Makefile的目錄(當然也在代碼所在的目錄)下輸入命令“make”。刪除已經(jīng)此目錄下之前使用“make”生成的文件(包括那些中間過程的.o文件),也只需要輸入命令“make clean”就可以了。 在這個Makefile中,我們的目標(target)就是可執(zhí)行文件“edit”和那些.o文件(main.o,kbd.o….);依賴(prerequisites)就是冒號后面的那些 .c 文件和 .h文件。所有的.o文件既是依賴(相對于可執(zhí)行程序edit)又是目標(相對于.c和.h文件)。命令包括 “cc –c maic.c”、“cc –c kbd.c”…… 當規(guī)則的目標是一個文件,在它的任何一個依賴文件被修改以后,在執(zhí)行“make”時這個目標文件將會被重新編譯或者重新連接。當然,此目標的任何一個依賴文件如果有必要則首先會被重新編譯。在這個例子中,“edit”的依賴為8個.o文件;而“main.o”的依賴文件為“main.c”和“defs.h”。當“main.c”或者“defs.h”被修改以后,再次執(zhí)行“make”,“main.o”就會被更新(其它的.o文件不會被更新),同時“main.o” 的更新將會導致“edit”被更新。 在描述依賴關系行之下通常就是規(guī)則的命令行(存在一些些規(guī)則沒有命令行),命令行定義了規(guī)則的動作(如何根據(jù)依賴文件來更新目標文件)。命令行必需以[Tab]鍵開始,以和Makefile其他行區(qū)別。就是說所有的命令行必需以[Tab] 字符開始,但并不是所有的以[Tab]鍵出現(xiàn)行都是命令行。但make程序會把出現(xiàn)在第一條規(guī)則之后的所有以[Tab]字符開始的行都作為命令行來處理。(記?。?/span>make程序本身并不關心命令是如何工作的,對目標文件的更新需要你在規(guī)則描述中提供正確的命令。“make”程序所做的就是當目標程序需要更新時執(zhí)行規(guī)則所定義的命令)。 目標“clean”不是一個文件,它僅僅代表執(zhí)行一個動作的標識。正常情況下,不需要執(zhí)行這個規(guī)則所定義的動作,因此目標“clean”沒有出現(xiàn)在其它任何規(guī)則的依賴列表中。因此在執(zhí)行make時,它所指定的動作不會被執(zhí)行。除非在執(zhí)行make時明確地指定它。而且目標“clean”沒有任何依賴文件,它只有一個目的,就是通過這個目標名來執(zhí)行它所定義的命令。Makefile中把那些沒有任何依賴只有執(zhí)行動作的目標稱為“偽目標”(phony targets)。需要執(zhí)行“clean”目標所定義的命令,可在shell下輸入:make clean。 2.4 make如何工作默認的情況下,make執(zhí)行的是Makefile中的第一個規(guī)則,此規(guī)則的第一個目標稱之為“最終目的”或者“終極目標”(就是一個Makefile最終需要更新或者創(chuàng)建的目標。 上例的Makefile,目標“edit”在Makefile中是第一個目標,因此它就是make的“終極目標”。當修改了任何C源文件或者頭文件后,執(zhí)行make將會重建終極目標“edit”。 當在shell提示符下輸入“make”命令以后。make讀取當前目錄下的Makefile文件,并將Makefile文件中的第一個目標作為其執(zhí)行的“終極目標”,開始處理第一個規(guī)則(終極目標所在的規(guī)則)。在我們的例子中,第一個規(guī)則就是目標“edit”所在的規(guī)則。規(guī)則描述了“edit”的依賴關系,并定義了鏈接.o文件生成目標“edit”的命令; make在執(zhí)行這個規(guī)則所定義的命令之前,首先處理目標“edit”的所有的依賴文件(例子中的那些.o文件)的更新規(guī)則(以這些.o文件為目標的規(guī)則)。對這些.o文件為目標的規(guī)則處理有下列三種情況: 1. 目標.o文件不存在,使用其描述規(guī)則創(chuàng)建它; 2. 目標.o文件存在,目標.o文件所依賴的.c源文件、.h文件中的任何一個比目標.o文件“更新”(在上一次make之后被修改)。則根據(jù)規(guī)則重新編譯生成它; 3. 目標.o文件存在,目標.o文件比它的任何一個依賴文件(的.c源文件、.h文件)“更新”(它的依賴文件在上一次make之后沒有被修改),則什么也不做。 這些.o文件所在的規(guī)則之所以會被執(zhí)行,是因為這些.o文件出現(xiàn)在“終極目標”的依賴列表中。在Makefile中一個規(guī)則的目標如果不是“終極目標”所依賴的(或者“終極目標”的依賴文件所依賴的),那么這個規(guī)則將不會被執(zhí)行,除非明確指定執(zhí)行這個規(guī)則(可以通過make的命令行指定重建目標,那么這個目標所在的規(guī)則就會被執(zhí)行,例如 “make clean”)。在編譯或者重新編譯生成一個.o文件時,make同樣會去尋找它的依賴文件的重建規(guī)則(是這樣一個規(guī)則:這個依賴文件在規(guī)則中作為目標出現(xiàn)),在這里就是.c和.h文件的重建規(guī)則。在上例的Makefile中沒有哪個規(guī)則的目標是.c或者.h文件,所以沒有重建.c和.h文件的規(guī)則。不過C言語源程序文件可以使用工具Bison或者Yacc來生成(具體用法可參考相應的手冊)。 完成了對.o文件的創(chuàng)建(第一次編譯)或者更新之后,make程序將處理終極目標“edit”所在的規(guī)則,分為以下三種情況: 1. 目標文件“edit”不存在,則執(zhí)行規(guī)則以創(chuàng)建目標“edit”。 2. 目標文件“edit”存在,其依賴文件中有一個或者多個文件比它“更新”,則根據(jù)規(guī)則重新鏈接生成“edit”。 3. 目標文件“edit”存在,它比它的任何一個依賴文件都“更新”,則什么也不做。 上例中,如果更改了源文件“insert.c”后執(zhí)行make,“insert.o”將被更新,之后終極目標“edit”將會被重生成;如果我們修改了頭文件“command.h”之后運行“make”,那么“kbd.o”、“command.o”和“files.o”將會被重新編譯,之后同樣終極目標“edit”也將被重新生成。 以上我們通過一個簡單的例子,介紹了Makefile中目標和依賴的關系。我們簡單總結一下:對于一個Makefile文件,“make”首先解析終極目標所在的規(guī)則(上節(jié)例子中的第一個規(guī)則),根據(jù)其依賴文件(例子中第一個規(guī)則的8個.o文件)依次(按照依賴文件列表從左到右的順序)尋找創(chuàng)建這些依賴文件的規(guī)則。首先為第一個依賴文件(main.o)尋找創(chuàng)建規(guī)則,如果第一個依賴文件依賴于其它文件(main.c、defs.h),則同樣為這個依賴文件尋找創(chuàng)建規(guī)則(創(chuàng)建main.c和defs.h的規(guī)則,通常源文件和頭文件已經(jīng)存在,也不存在重建它們的規(guī)則)……,直到為所有的依賴文件找到合適的創(chuàng)建規(guī)則。之后make從最后一個規(guī)則(上例目標為main.o的規(guī)則)回退開始執(zhí)行,最終完成終極目標的第一個依賴文件的創(chuàng)建和更新。之后對第二個、第三個、第四個……終極目標的依賴文件執(zhí)行同樣的過程(上例的的順序是“main.o”、“kbd.o”、“command.o”……)。 創(chuàng)建或者更新每一個規(guī)則依賴文件的過程都是這樣的一個過程(類似于c語言中的遞歸過程)。對于任意一個規(guī)則執(zhí)行的過程都是按照依賴文件列表順序,對于規(guī)則中的每一個依賴文件,使用同樣方式(按照同樣的過程)去重建它,在完成對所有依賴文件的重建之后,最后一步才是重建此規(guī)則的目標。 更新(或者創(chuàng)建)終極目標的過程中,如果任何一個規(guī)則執(zhí)行出現(xiàn)錯誤make就立即報錯并退出。整個過程make只是負責執(zhí)行規(guī)則,而對具體規(guī)則所描述的依賴關系的正確性、規(guī)則所定義的命令的正確性不做任何判斷。就是說,一個規(guī)則的依賴關系是否正確、描述重建目標的規(guī)則命令行是否正確,make不做任何錯誤檢查。 因此,需要正確的編譯一個工程。需要在提供給make程序的Makefile中來保證其依賴關系的正確性、和執(zhí)行命令的正確性。 2.5 指定變量同樣是上邊的例子,我們來看一下終極目標“edit”所在的規(guī)則:
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
在這個規(guī)則中.o文件列表出現(xiàn)了兩次;第一次:作為目標“edit”的依賴文件列表出現(xiàn),第二次:規(guī)則命令行中作為“cc”的參數(shù)列表。這樣做所帶來的問題是:如果我們需要為目標“edit”增加一個的依賴文件,我們就需要在兩個地方添加(依賴文件列表和規(guī)則的命令中)。添加時可能在“edit”的依賴列表中加入了、但卻忘記了給命令行中添加,或者相反。這就給后期的維護和修改帶來了很多不方便,添加或修改時出現(xiàn)遺漏。 為了避免這個問題,在實際工作中大家都比較認同的方法是,使用一個變量“objects”、“OBJECTS”、“objs”、“OBJS”、“obj”或者“OBJ”來作為所有的.o文件的列表的替代。在使用到這些文件列表的地方,使用此變量來代替。在上例的Makefile中我們可以添加這樣一行:
objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
“objects”作為一個變量,它代表所有的.o文件的列表。在定義了此變量后,我們就可以在需要使用這些.o文件列表的地方使用“$(objects)”來表示它,而不需要羅列所有的.o文件列表(變量可參考 第六章 使用變量)。因此上例的規(guī)則就可以這樣寫:
objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) ……. ……. clean : rm edit $(objects) 當我們需要為終極目標“edit”增加或者去掉一個.o依賴文件時,只需要改變“objects”的定義(加入或者去掉若干個.o文件)。這樣做不但減少書寫的工作量,而且可以減少修改而產(chǎn)生錯誤的可能。 2.6 自動推導規(guī)則在使用make編譯.c源文件時,編譯.c源文件規(guī)則的命令可以不用明確給出。這是因為make本身存在一個默認的規(guī)則,能夠自動完成對.c文件的編譯并生成對應的.o文件。它執(zhí)行命令“cc -c”來編譯.c源文件。在Makefile中我們只需要給出需要重建的目標文件名(一個.o文件),make會自動為這個.o文件尋找合適的依賴文件(對應的.c文件。對應是指:文件名除后綴外,其余都相同的兩個文件),而且使用正確的命令來重建這個目標文件。對于上邊的例子,此默認規(guī)則就使用命令“cc -c main.c -o main.o”來創(chuàng)建文件“main.o”。對一個目標文件是“N.o”,倚賴文件是“N.c”的規(guī)則,完全可以省略其規(guī)則的命令行,而由make自身決定使用默認命令。此默認規(guī)則稱為make的隱含規(guī)則。 這樣,在書寫Makefile時,我們就可以省略掉描述.c文件和.o依賴關系的規(guī)則,而只需要給出那些特定的規(guī)則描述(.o目標所需要的.h文件)。因此上邊的例子就可以以更加簡單的方式書寫,我們同樣使用變量“objects”。Makefile內容如下:
# sample Makefile objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
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)
這種格式的Makefile更接近于我們實際應用。(關于目標“clean”的詳細說明我們在后邊進行) make的隱含規(guī)則在實際工程的make中會經(jīng)常使用,它使得編譯過程變得方便。幾乎在所有的Makefile中都用到了make的隱含規(guī)則,make的隱含規(guī)則是非常重要的一個概念。后續(xù)我們會在第十章會有專門的討論。2.7 另類風格的makefileMakefile中,目標使用隱含規(guī)則生成,我們就可以也可以書寫另外一種風格Makefile。在這個Makefile中,根據(jù)依賴而不是目標對規(guī)則進行分組。上例的Makefile就可以這樣來實現(xiàn):
#sample Makefile objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
edit : $(objects) cc -o edit $(objects)
$(objects) : defs.h kbd.o command.o files.o : command.h display.o insert.o search.o files.o : buffer.h
例子中頭文件“defs.h”作為所有.o文件的依賴文件。其它兩個頭文件作為其對應規(guī)則的目標中所列舉的所有.o文件的依賴文件。 但是這種風格的Makefile并不值得我們借鑒。問題在于:同時把多個目標文件的依賴放在同一個規(guī)則中進行描述(一個規(guī)則中含有多個目標文件),這樣導致規(guī)則定義不明了,比較混亂。建議大家不要在Makefile中采用這種方式了書寫。否則后期維護將會是一件非常痛苦的事情。 書寫規(guī)則建議的方式是:單目標,多依賴。就是說盡量要做到一個規(guī)則中只存在一個目標文件,可有多個依賴文件。盡量避免多目標,單依賴的方式。這樣后期維護也會非常方便,而且Makefile會更清晰、明了。 2.8 清除工作目錄過程文件規(guī)則除了完成源代碼編譯之外,也可以完成其它任務。例如:前邊提到的為了實現(xiàn)清除當前目錄中編譯過程中產(chǎn)生的臨時文件(edit和哪些.o文件)的規(guī)則:
clean : rm edit $(objects)
在實際應用時,我們把這個規(guī)則寫成如下稍微復雜一些的樣子。以防止出現(xiàn)始料未及的情況。 .PHONY : clean clean : -rm edit $(objects)
這兩個實現(xiàn)有兩點不同: 1. 通過“.PHONY”特殊目標將“clean”目標聲明為偽目標。避免當磁盤上存在一個名為“clean”文件時,目標“clean”所在規(guī)則的命令無法執(zhí)行。2. 在命令行之前使用“-”,意思是忽略命令“rm”的執(zhí)行錯誤。 這樣的一個目標在Makefile中,不能將其作為終極目標(Makefile的第一個目標)。因為我們的初衷并不是當你在命令行上輸入make以后執(zhí)行刪除動作。而是要創(chuàng)建或者更新程序。在我們上邊的例子中。就是在輸入make以后要需要對目標“edit”進行創(chuàng)建或者重建。 上例中因為目標“clean”沒有出現(xiàn)在終極目標“edit”依賴關系中(終極目標的直接依賴或者間接依賴),所以我們執(zhí)行“make”時,目標“clean”所在的規(guī)則將不會被處理。當需要執(zhí)行此規(guī)則,要在make的命令行選項中明確指定這個目標(執(zhí)行“make clean”)。關于make的執(zhí)行可參考 9.2 指定終極目標 一節(jié)。 |
|
|