Makefile概述

你需要 makefile 文件來告訴make應該做些什麼。通常,makefile 會告訴make如何編譯和鏈接一個程序。

在本章中,我們將討論一個簡單的 makefile 文件,該文件描述了如何編譯和鏈接由八個C源文件和三個頭文件組成的文本編輯器。makefile 也可以告訴make在被明確要求時如何運行各種各樣不同的命令(例如,將某些文件進行清理操作移除掉)。要查看更複雜的 makefile 示例,請參閱[Complex Makefile]

make重新編譯編輯器時,必須重新編譯每個更改了的C源文件。如果頭文件已更改,則必須重新編譯每個包含該頭文件的C源文件以確保安全。每個編譯生成一個對應於源文件的目標文件。最後,如果任何源文件已經被重新編譯,所有的目標文件,無論是新建立的還是從先前的編譯保存的,都必須鏈接在一起以產生新的可執行的編輯器。

2.1 規則(rule)

一個簡單的makefile由下列形式的「規則(rule)」組成:

target ... : prerequisites ... recipe ... ...

target 通常是由程序所生成的文件的名稱;例如,target 是可執行文件或目標文件。target 也可以是所要執行的操作的名稱,例如「clean」(請參閱[Phony Targets])。

prerequisites 作為輸入,用於創建 target 的文件。target 通常依賴於多個文件。

recipemake所執行的操作。recipe 可能有多個命令,可以在同一行上,也可以分別在各自的行上。請注意:你需要在每個 recipe 行的輸入放置製表符!這是一個容易出錯的地方。如果你希望在 recipe 中添加製表符以外的其他字元,可以將變數.RECIPEPREFIX設置為替代字元(請參見[Special Variables])。

通常情況下,recipe 需要具有 prerequisites ,並且在任何 prerequisites 發生更改時用於創建目標文件。但是,有些 rule 指定了 target 所要執行的 recipe,那麼這些 rule 就不需要 prerequisites 了。例如,包含刪除命令的 targetclean」的 rule 就不具有 prerequisites

然後,rule 解釋了如何以及何時重新製作特定文件,這個文件就是指這個 ruletargetmake執行 prerequisites 所對應的 recipe 來創建或者更新 targetrule 也可以解釋如何以及何時執行一個操作。參見[Writing Rules]

一個 makefile 可能包含除 rule 以外的其他文本,但一個簡單的 makefile 只需要包含 rulerule 可能看起來比這個模板中所顯示的要複雜一些,但都或多或少地符合這個模式。

2.2 一個簡單的 Makefile

下面是一個簡單的 makefile,它描述了一個名為edit的可執行文件依賴於八個目標文件的方式,而這些目標文件依次取決於八個C源文件和三個頭文件。

在本例中,所有C文件都包含defs.h,但只有那些定義編輯命令的文件包含command.h,並且只有用於更改編輯器緩衝區的低級文件才包含buffer.h

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

我們使用反斜杠/換行符將每條長的命令分成兩行;雖然可以使用一個長的命令行,但短的命令行更容易閱讀。請參見[Splitting Long Lines]

要使用此 makefile 創建名為edit的可執行文件,請鍵入:

make

要使用此 makefile 從文件夾中刪除可執行文件和所有目標文件,請鍵入:

make clean

在示例 makefile 中,target 包括可執行文件edit以及目標文件main.okbd.oprerequisites 是諸如main.cdefs.h之類的文件。實際上,每個.o文件既是 target 又是 prerequisitesrecipe 包括cc -c main.ccc -c kbd.c

target 是文件時,如果其任何 prerequisites 發生更改,則需要重新編譯或重新鏈接。另外,任何自動生成的 prerequisites 都應該被先更新。在這個例子中,edit依賴於八個目標文件中的每一個;目標文件main.o依賴於源文件main.c和頭文件defs.h

recipe 緊隨著包含 targetprerequisites 的那一行。這些 recipe 說明了如何更新 target 文件。製表符(或任何由.RECIPEPREFIX變數指定的字元;參見[Special Variables])必須位於 recipe 中每行的開頭,以便將 recipe 與 makefile 中的其他行區分開來。(請記住,make並不知道 recipe 如何運行。您需要提供能夠正確更新目標文件的 recipemake所能做的就是在 target 文件需要更新時執行你指定的 recipe。)

target clean不是一個文件,而只是一個操作的名稱。由於通常不希望執行 rule 中的這個操作,因此clean不是任何其他 ruleprerequisites。因此,除非你特別說明,否則make不會做任何事情。請注意,這個 ruleclean)不僅不是 prerequisites,也不具有任何 prerequisites,因此這個 rule 的唯一目的就是運行特定的 recipe。這種不指向文件而只是進行某種操作的 target 被稱為虛假目標(phony targets)。有關此類 target 的信息,請參見[phony targets]。請參閱[Errors in Recipes],了解如何使make忽略來自rm或任何其他命令的錯誤。

2.3 make如何處理 Makefile

默認情況下,make從第一個 target 開始(不是以.開頭的 target)。這被稱為默認目標(default goal)。(goalmake最終更新的 target。)你可以使用命令行(請參見 [Arguments to Specify the Goals])或使用特殊變數.DEFAULT_GOAL(參見[Other Special Variables])來覆蓋此操作。

在上一節的簡單示例中,default goal 是指更新可執行程序edit;因此,我們把這條 rule 放在了最前面。

因此,當你發出命令:

make

make 讀取當前目錄中的 makefile 並從處理第一條 rule 開始。在該示例中,此 rule 用於重新鏈接edit;但是在make可以完全處理這個 rule 之前,它必須處理edit所依賴文件的 rule,在這個例子中edit所依賴的文件就是目標文件。每個文件都按照自己的 rule 進行處理。這些 rule 就是指通過編譯其源文件來更新每個.o文件。如果源文件或任何聲明為 prerequisites 的頭文件比目標文件(object file)更新,或者目標文件不存在,則必須重新編譯。

其他 rule 被處理是因為它們的 target 出現在 goalprerequisites 里。如果其他的 rule 沒有被 goal(或者其他依賴項)所依賴,那麼這些 rule 將不會被處理,除非指定make去處理(使用諸如 make clean 之類的命令)。

在重新編譯目標文件之前,make會更新它的 prerequisites ——源文件和頭文件。在這個 makefile 中,將不會對他們執行任何操作——.c.h文件不是任何 ruletarget——因此make不會對它們執行任何操作。但make會更新那些自動生成的C程序,比如Bison或Yacc自己制定的 rule

在重新編譯需要的目標文件之後,make決定是否重新鏈接edit。如果文件edit不存在或者有目標文件比它更新,那麼必須要重新鏈接edit。如果目標文件被重新編譯了,那麼它現在就比edit更新了,因此edit要被重新鏈接。

因此,如果我們改變了文件insert.c並運行makemake將會編譯該文件來更新insert.o,然後再鏈接edit。如果我們改變了文件command.h並運行makemake將會重新編譯目標文件kbd.ocommand.ofiles.o,然後再鏈接文件edit

2.4 運用變數使 makefile 更簡潔

例子中,我們在editrule 部分列出了目標文件兩次:

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

這種重複是容易出錯的;如果一個新的目標文件被添加到系統中,我們可能只會將它添加到一個列表中而忘記將它添加到另一個列表中。我們可以通過使用變數(Variables)來消除風險並簡化 makefile 。Variables 允許定義一次文本字元串,並在後面的多個位置進行替換(請參閱[How to Use Variables])。

每個 makefile 的標準做法是使用名為objectsOBJECTSobjsOBJSobjOBJ的變數,它是所有目標文件名的列表。我們將在 makefile 中用這樣一行來定義這樣一個變數objects

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

然後,在我們想要輸入目標文件名列表的地方,我們就可以通過輸入$(objects)來替代變數的值(參見[How to Use Variables])。

以下是使用變數替代目標文件時經過簡化的 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 : 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 $(objects)

2.5 讓make推斷 Recipes

沒有必要詳細說明用於編譯單個C源文件的 recipe ,因為make可以推斷出它們:使用cc -c命令從相應的.c文件更新.o文件時,具有隱式規則(implicit rule)。例如,將使用 recipe cc -c main.c -o main.o來把main.c編譯成main.o。因此,我們可以從目標文件的 rule 中省略 recipe 。 參見[Using Implicit Rules]

.c文件自動使用這種方式的時候,它會被自動添加到 prerequisites 的列表中。因此,只要省略掉 recipe 我們就可以從 prerequisites 中省略.c文件。

下面是整個例子,包括這兩個變化,以及上面提出的一個變數objects

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」相關的複雜情況在其他地方有描述,參見[Phony Targets][Errors in Recipes]

因為隱式規則非常方便,所以它們很重要。 你會看到他們經常被使用。

2.6 另一種風格的 makefile

當 makefile 的目標(objects)都由隱式規則創建時,可以使用其他類型的 makefile 。在這種類型的 makefile 中,你可以按照它們的 prerequisites 而不是它們的 target 對條目進行分組。下面是一個例子:

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是所有目標文件的 prerequisitescommand.hbuffer.h是那些列出了它們的目標文件的 prerequisites

這種風格是否更好是一個喜好問題:它更緊湊,但有些人不喜歡它,因為他們認為把每個目標(objects)的所有信息放在一個地方更清晰。

2.7 清除文件夾的 rule

編譯一個程序可能不是你所寫的 rule 的唯一操作。 makefile 通常會描述一些除了編譯程序之外的其他操作:例如,如何刪除目標文件和可執行文件,使文件夾變得clean

下面是我們應該如何寫一個makerule 來清空我們的例子編輯器:

clean: rm edit $(objects)

在實踐中,我們可能希望以更複雜的方式編寫 rule 來處理意外情況。我們將會這樣做:

.PHONY : clean clean : -rm edit $(objects)

這樣可以避免make在名為clean的文件存在時產生錯誤,並在rm產生錯誤時繼續執行。(參見[Phony Targets][Errors in Recipes]

像這樣的 rule 不應該被放置在 makefile 的開頭,因為我們不想要它們被默認運行。因此,在例子中的 makefile ,我們想要editrule ——重新編譯編輯器——仍然是 default goal

因為clean不是editprerequisites ,所以如果我們只輸入make命令而不加任何參數,那麼這個 rule 根本不會被執行。為了使這個 rule 被執行,我們需要輸入make clean。詳見[How to Run make]


推薦閱讀:

Makefile 與 IDE 相比效率高在哪?
利用debug庫實現對lua的性能分析
makefile中的通配符使用?
make makefile cmake qmake都是什麼,有什麼區別?
使用xmake優雅地描述工程

TAG:推薦 | 互聯網 | Linux | Makefile | GCC |