第二章: GNU make 介紹

 

第二章 GNU make 介紹

make在執行時,需要一個命名爲Makefile的文件。這個文件告訴make以何種方式編譯源代碼和鏈接程序。典型地,可執行文件可由一些.o文件按照一定的順序生成或者更新。如果在你的工程中已經存在一個活着多個正確的Makefile。當對工程中的若干源文件修改以後,需要根據修改來更新可執行文件或者庫文件,正如前面提到的你只需要在shell下執行“make”。make會自動根據修改情況完成源文件的對應.o文件的更新、庫文件的更新、最終的可執行程序的更新。

make通過比較對應文件(規則的目標和依賴,)的最後修改時間,來決定哪些文件需要更新、那些文件不需要更新。對需要更新的文件make就執行數據庫中所記錄的相應命令(在make讀取Makefile以後會建立一個編譯過程的描述數據庫。此數據庫中記錄了所有各個文件之間的相互關係,以及它們的關係描述)來重建它,對於不需要重建的文件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:

 

         [email protected]

 

or use our Web-based project management tool, at:

 

         http://savannah.gnu.org/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過程中。發現bug或者問題。可以通過以上的方式和渠道反饋。

好了。開始我們的神奇之旅吧!

2.1  Makefile簡介

在執行make之前,需要一個命名爲Makefile的特殊文件(本文的後續將使用Makefile作爲這個特殊文件的文件名)來告訴make需要做什麼(完成什麼任務),該怎麼做。通常,make工具主要被用來進行工程編譯和程序鏈接。

本節將分析一個簡單的Makefile,它對一個包含8個C的源代碼和三個頭文件的工程進行編譯和鏈接。這個Makefile提供給了make必要的信息,make程序根據Makefile中的規則描述執行相關的命令來完成指定的任務(如:編譯、鏈接和清除編譯過程文件等)。複雜的Makefile我們將會在本文後續進行討論。

當使用make工具進行編譯時,工程中以下幾種文件在執行make時將會被編譯(重新編譯):

1.        所有的源文件沒有被編譯過,則對各個C源文件進行編譯並進行鏈接,生成最後的可執行程序;

2.        每一個在上次執行make之後修改過的C源代碼文件在本次執行make時將會被重新編譯;

3.        頭文件在上一次執行make之後被修改。則所有包含此頭文件的C源文件在本次執行make時將會被重新編譯。

後兩種情況是make只將修改過的C源文件重新編譯生成.o文件,對於沒有修改的文件不進行任何工作。重新編譯過程中,任何一個源文件的修改將產生新的對應的.o文件,新的.o文件將和以前的已經存在、此次沒有重新編譯的.o文件重新連接生成最後的可執行程序。

首先讓我們先來看一些Makefile相關的基本知識。 

2.2 Makefile規則介紹

一個簡單的Makefile描述規則組成:

TARGET... : PREREQUISITES...

COMMAND

...

...

target:規則的目標。通常是最後需要生成的文件名或者爲了實現這個目的而必需的中間過程文件名。可以是.o文件、也可以是最後的可執行程序的文件名等。另外,目標也可以是一個make執行的動作的名稱,如目標“clean”,我們稱這樣的目標是“僞目標”。

prerequisites:規則的依賴。生成規則目標所需要的文件名列表。通常一個目標依賴於一個或者多個文件。

command:規則的命令行。是規則所要執行的動作(任意的shell命令或者是可在shell下執行的程序)。它限定了make執行這條規則時所需要的動作。

一個規則可以有多個命令行,每一條命令佔一行。注意:每一個命令行必須以[Tab]字符開始,[Tab]字符告訴make此行是一個命令行。make按照命令完成相應的動作。這也是書寫Makefile中容易產生,而且比較隱蔽的錯誤。

命令就是在任何一個目標的依賴文件發生變化後重建目標的動作描述。一個目標可以沒有依賴而只有動作(指定的命令)。比如Makefile中的目標“clean”,此目標沒有依賴,只有命令。它所定義的命令用來刪除make過程產生的中間文件(進行清理工作)。

在Makefile中“規則”就是描述在什麼情況下、如何重建規則的目標文件,通常規則中包括了目標的依賴關係(目標的依賴文件)和重建目標的命令。make執行重建目標的命令,來創建或者重建規則的目標(此目標文件也可以是觸發這個規則的上一個規則中的依賴文件)。規則包含了文件之間的依賴關係和更新此規則目標所需要的命令。

一個Makefile文件中通常還包含了除規則以外的很多東西(後續我們會一步一步的展開)。一個最簡單的Makefile可能只包含規則。規則在有些Makefile中可能看起來非常複雜,但是無論規則的書寫是多麼的複雜,它都符合規則的基本格式。

make程序根據規則的依賴關係,決定是否執行規則所定義的命令的過程我們稱之爲執行規則

2.3 簡單的示例

本小節開始我們在第一小節中提到的例子。此例子由3個頭文件和8個C文件組成。我們將書寫一個簡單的Makefile,來描述如何創建最終的可執行文件“edit”,此可執行文件依賴於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以後;需要創建可執行程序“edit”,所要做的就是在包含此Makefile的目錄(當然也在代碼所在的目錄)下輸入命令“make”。刪除已經此目錄下之前使用“make”生成的文件(包括那些中間過程的.o文件),也只需要輸入命令“make clean”就可以了。

在這個Makefile中,我們的目標(target)就是可執行文件“edit”和那些.o文件(main.o,kbd.o….);依賴(prerequisites)就是冒號後面的那些 .c 文件和 .h文件。所有的.o文件既是依賴(相對於可執行程序edit)又是目標(相對於.c和.h文件)。命令包括 “cc –c maic.c”、“cc –c kbd.c”……

當規則的目標是一個文件,在它的任何一個依賴文件被修改以後,在執行“make”時這個目標文件將會被重新編譯或者重新連接。當然,此目標的任何一個依賴文件如果有必要則首先會被重新編譯。在這個例子中,“edit”的依賴爲8個.o文件;而“main.o”的依賴文件爲“main.c”和“defs.h”。當“main.c”或者“defs.h”被修改以後,再次執行“make”,“main.o”就會被更新(其它的.o文件不會被更新),同時“main.o” 的更新將會導致“edit”被更新。

在描述依賴關係行之下通常就是規則的命令行(存在一些些規則沒有命令行),命令行定義了規則的動作(如何根據依賴文件來更新目標文件)。命令行必需以[Tab]鍵開始,以和Makefile其他行區別。就是說所有的命令行必需以[Tab] 字符開始,但並不是所有的以[Tab]鍵出現行都是命令行。但make程序會把出現在第一條規則之後的所有以[Tab]字符開始的行都作爲命令行來處理。(記住:make程序本身並不關心命令是如何工作的,對目標文件的更新需要你在規則描述中提供正確的命令。“make”程序所做的就是當目標程序需要更新時執行規則所定義的命令)。

目標“clean”不是一個文件,它僅僅代表執行一個動作的標識。正常情況下,不需要執行這個規則所定義的動作,因此目標“clean”沒有出現在其它任何規則的依賴列表中。因此在執行make時,它所指定的動作不會被執行。除非在執行make時明確地指定它。而且目標“clean”沒有任何依賴文件,它只有一個目的,就是通過這個目標名來執行它所定義的命令。Makefile中把那些沒有任何依賴只有執行動作的目標稱爲“僞目標”(phony targets)。需要執行“clean”目標所定義的命令,可在shell下輸入:make clean。 

2.4   make如何工作

默認的情況下,make執行的是Makefile中的第一個規則,此規則的第一個目標稱之爲“最終目的”或者“終極目標”(就是一個Makefile最終需要更新或者創建的目標。

上例的Makefile,目標“edit”在Makefile中是第一個目標,因此它就是make的“終極目標”。當修改了任何C源文件或者頭文件後,執行make將會重建終極目標“edit”。

當在shell提示符下輸入“make”命令以後。make讀取當前目錄下的Makefile文件,並將Makefile文件中的第一個目標作爲其執行的“終極目標”,開始處理第一個規則(終極目標所在的規則)。在我們的例子中,第一個規則就是目標“edit”所在的規則。規則描述了“edit”的依賴關係,並定義了鏈接.o文件生成目標“edit”的命令; make在執行這個規則所定義的命令之前,首先處理目標“edit”的所有的依賴文件(例子中的那些.o文件)的更新規則(以這些.o文件爲目標的規則)。對這些.o文件爲目標的規則處理有下列三種情況:

1.        目標.o文件不存在,使用其描述規則創建它;

2.        目標.o文件存在,目標.o文件所依賴的.c源文件、.h文件中的任何一個比目標.o文件“更新”(在上一次make之後被修改)。則根據規則重新編譯生成它;

3.        目標.o文件存在,目標.o文件比它的任何一個依賴文件(的.c源文件、.h文件)“更新”(它的依賴文件在上一次make之後沒有被修改),則什麼也不做。

這些.o文件所在的規則之所以會被執行,是因爲這些.o文件出現在“終極目標”的依賴列表中。在Makefile中一個規則的目標如果不是“終極目標”所依賴的(或者“終極目標”的依賴文件所依賴的),那麼這個規則將不會被執行,除非明確指定執行這個規則(可以通過make的命令行指定重建目標,那麼這個目標所在的規則就會被執行,例如 “make clean”)。在編譯或者重新編譯生成一個.o文件時,make同樣會去尋找它的依賴文件的重建規則(是這樣一個規則:這個依賴文件在規則中作爲目標出現),在這裏就是.c和.h文件的重建規則。在上例的Makefile中沒有哪個規則的目標是.c或者.h文件,所以沒有重建.c和.h文件的規則。不過C言語源程序文件可以使用工具Bison或者Yacc來生成(具體用法可參考相應的手冊)。

完成了對.o文件的創建(第一次編譯)或者更新之後,make程序將處理終極目標“edit”所在的規則,分爲以下三種情況:

1.        目標文件“edit”不存在,則執行規則以創建目標“edit”。

2.        目標文件“edit”存在,其依賴文件中有一個或者多個文件比它“更新”,則根據規則重新鏈接生成“edit”。

3.        目標文件“edit”存在,它比它的任何一個依賴文件都“更新”,則什麼也不做。

上例中,如果更改了源文件“insert.c”後執行make,“insert.o”將被更新,之後終極目標“edit”將會被重生成;如果我們修改了頭文件“command.h”之後運行“make”,那麼“kbd.o”、“command.o”和“files.o”將會被重新編譯,之後同樣終極目標“edit”也將被重新生成。

以上我們通過一個簡單的例子,介紹了Makefile中目標和依賴的關係。我們簡單總結一下:對於一個Makefile文件,“make”首先解析終極目標所在的規則(上節例子中的第一個規則),根據其依賴文件(例子中第一個規則的8個.o文件)依次(按照依賴文件列表從左到右的順序)尋找創建這些依賴文件的規則。首先爲第一個依賴文件(main.o)尋找創建規則,如果第一個依賴文件依賴於其它文件(main.c、defs.h),則同樣爲這個依賴文件尋找創建規則(創建main.c和defs.h的規則,通常源文件和頭文件已經存在,也不存在重建它們的規則)……,直到爲所有的依賴文件找到合適的創建規則。之後make從最後一個規則(上例目標爲main.o的規則)回退開始執行,最終完成終極目標的第一個依賴文件的創建和更新。之後對第二個、第三個、第四個……終極目標的依賴文件執行同樣的過程(上例的的順序是“main.o”、“kbd.o”、“command.o”……)。

創建或者更新每一個規則依賴文件的過程都是這樣的一個過程(類似於c語言中的遞歸過程)。對於任意一個規則執行的過程都是按照依賴文件列表順序,對於規則中的每一個依賴文件,使用同樣方式(按照同樣的過程)去重建它,在完成對所有依賴文件的重建之後,最後一步纔是重建此規則的目標。

更新(或者創建)終極目標的過程中,如果任何一個規則執行出現錯誤make就立即報錯並退出。整個過程make只是負責執行規則,而對具體規則所描述的依賴關係的正確性、規則所定義的命令的正確性不做任何判斷。就是說,一個規則的依賴關係是否正確、描述重建目標的規則命令行是否正確,make不做任何錯誤檢查。

因此,需要正確的編譯一個工程。需要在提供給make程序的Makefile中來保證其依賴關係的正確性、和執行命令的正確性。

2.5 指定變量

同樣是上邊的例子,我們來看一下終極目標“edit”所在的規則:

 

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

 

在這個規則中.o文件列表出現了兩次;第一次:作爲目標“edit”的依賴文件列表出現,第二次:規則命令行中作爲“cc”的參數列表。這樣做所帶來的問題是:如果我們需要爲目標“edit”增加一個的依賴文件,我們就需要在兩個地方添加(依賴文件列表和規則的命令中)。添加時可能在“edit”的依賴列表中加入了、但卻忘記了給命令行中添加,或者相反。這就給後期的維護和修改帶來了很多不方便,添加或修改時出現遺漏。

爲了避免這個問題,在實際工作中大家都比較認同的方法是,使用一個變量“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文件列表(變量可參考 第六章 使用變量)。因此上例的規則就可以這樣寫:

 

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文件)。這樣做不但減少書寫的工作量,而且可以減少修改而產生錯誤的可能。 

2.6 自動推導規則

在使用make編譯.c源文件時,編譯.c源文件規則的命令可以不用明確給出。這是因爲make本身存在一個默認的規則,能夠自動完成對.c文件的編譯並生成對應的.o文件。它執行命令“cc -c”來編譯.c源文件。在Makefile中我們只需要給出需要重建的目標文件名(一個.o文件),make會自動爲這個.o文件尋找合適的依賴文件(對應的.c文件。對應是指:文件名除後綴外,其餘都相同的兩個文件),而且使用正確的命令來重建這個目標文件。對於上邊的例子,此默認規則就使用命令“cc -c main.c -o main.o”來創建文件“main.o”。對一個目標文件是“N.o”,倚賴文件是“N.c”的規則,完全可以省略其規則的命令行,而由make自身決定使用默認命令。此默認規則稱爲make的隱含規則

這樣,在書寫Makefile時,我們就可以省略掉描述.c文件和.o依賴關係的規則,而只需要給出那些特定的規則描述(.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的隱含規則在實際工程的make中會經常使用,它使得編譯過程變得方便。幾乎在所有的Makefile中都用到了make的隱含規則,make的隱含規則是非常重要的一個概念。後續我們會在第十章會有專門的討論。 

2.7 另類風格的makefile

Makefile中,目標使用隱含規則生成,我們就可以也可以書寫另外一種風格Makefile。在這個Makefile中,根據依賴而不是目標對規則進行分組。上例的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)

 

$(objects) : defs.h

kbd.o command.o files.o : command.h

display.o insert.o search.o files.o : buffer.h

 

例子中頭文件“defs.h”作爲所有.o文件的依賴文件。其它兩個頭文件作爲其對應規則的目標中所列舉的所有.o文件的依賴文件。

但是這種風格的Makefile並不值得我們借鑑。問題在於:同時把多個目標文件的依賴放在同一個規則中進行描述(一個規則中含有多個目標文件),這樣導致規則定義不明瞭,比較混亂。建議大家不要在Makefile中採用這種方式了書寫。否則後期維護將會是一件非常痛苦的事情。

書寫規則建議的方式是:單目標,多依賴。就是說盡量要做到一個規則中只存在一個目標文件,可有多個依賴文件。儘量避免多目標,單依賴的方式。這樣後期維護也會非常方便,而且Makefile會更清晰、明瞭。 

2.8   清除工作目錄過程文件

規則除了完成源代碼編譯之外,也可以完成其它任務。例如:前邊提到的爲了實現清除當前目錄中編譯過程中產生的臨時文件(edit和哪些.o文件)的規則:

 

clean :

rm edit $(objects)

 

在實際應用時,我們把這個規則寫成如下稍微複雜一些的樣子。以防止出現始料未及的情況。

.PHONY : clean

clean :

-rm edit $(objects)

 

這兩個實現有兩點不同: 1. 通過“.PHONY”特殊目標將“clean”目標聲明爲僞目標。避免當磁盤上存在一個名爲“clean”文件時,目標“clean”所在規則的命令無法執行。2. 在命令行之前使用“-”,意思是忽略命令“rm”的執行錯誤。

這樣的一個目標在Makefile中,不能將其作爲終極目標(Makefile的第一個目標)。因爲我們的初衷並不是當你在命令行上輸入make以後執行刪除動作。而是要創建或者更新程序。在我們上邊的例子中。就是在輸入make以後要需要對目標“edit”進行創建或者重建。

上例中因爲目標“clean”沒有出現在終極目標“edit”依賴關係中(終極目標的直接依賴或者間接依賴),所以我們執行“make”時,目標“clean”所在的規則將不會被處理。當需要執行此規則,要在make的命令行選項中明確指定這個目標(執行“make clean”)。關於make的執行可參考 9.2 指定終極目標 一節。
發佈了13 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章