第九章:執行make

 

第九章:執行make


一般描述整個工程編譯規則的Makefile可以通過不止一種方式來執行。最簡單直接的方法就是使用不帶任何參數的“make”命令來重新編譯所有過時的文件。通常我們的Makefile就書寫爲這種方式。

在某些情況下:

1.        可能需要使用make更新一部分過時文件而不是全部

2.        需要使用另外的編譯器或者重新定義編譯選項

3.        只需要察看那些文件被修改,而不需要重新編譯

爲了達到這些特殊的目的,需要使用make的命令行參數來實現。Make的命令行參數能實現的功能不僅限於這些,通過make的命令行參數可以實現很多特殊功能。

另外,make的退出狀態有三種:

0 — 狀態爲0時,表示執行成功。

2 — 執行過程出現錯誤,同時會提示錯誤信息。

1 — 在執行make時使用了“-q”參數,而且當前工程中存在過時的目標文件。

 

本章的內容主要講述如何使用make的命令參數選項來實現一些特殊的目的。在本章最後會對make的命令行參數選項進行比較詳細的討論。

9.1      指定makefile文件

本文以前的部分對如何指定makefile文件已經有過介紹。當需要將一個普通命名的文件作爲makefile文件時,需要使用make的“-f”、“--file”或者“--makefile”選項來指定。例如:“make –f altmake”,它的意思是告訴make將文件“altmake”作爲makefile文件來解析執行。

當在make的命令行選項中出現多個“-f”參數時,所有通過“-f”參數指定的文件都被作爲make解析執行的makefile文件。

默認情況,在沒有使用“-f”(“--file”或者“--makefile”)指定文件時。make會在工作目錄(當前目錄)依次搜索命名爲“GNUmakefile”、“makefile”和“Makefile”的文件,最終解析執行的是這三個文件中首先搜索到的哪一個。

9.2      指定終極目標

“終極目標”的基本概念我們在前面已經提到過。所謂終極目標就是make最終所要重建的Makefile某個規則的目標(也可以稱之爲“最終規則”)。爲了完成對終極目標的重建,可能會觸發它的依賴或者依賴的依賴文件被重建的過程。

默認情況下,終極目標就是出現在Makefile中,除以點號“.”開始的第一個規則中的第一個目標(如果第一個規則存在多個目標)。因此Makefile書寫時需要保證:第一個目標的編譯規則就描述了整個工程或者程序的編譯過程和規則。如果Makefile的第一個規則有多個目標,那麼默認的終極目標是多個目標中的第一個。我們在Makefile所在的目錄下執行“make”時,將完成對默認終極目標的重建。

另外,也可以通過命令行將一個Makefile中的目標指定爲此次make過程的終極目標,替代默認的終極目標。使用Makefile中目標名作爲參數來執行“make”(格式爲 “make TARGET_NAME”,如:“make clean”),可以把這個目標指定爲終極目標。使用這種方式,我們也可以同時指定多個終極目標。

任何出現在Makefile規則中的目標都可以被指定爲終極目標(不包含以“-”開始的和包含“=”的賦值語句,一般它們也不會作爲一個目標出現),甚至可以指定一個在Makefile中不存在的目標作爲終極目標,前提是存在一個對應的隱含規則能夠實現對這個目標的重建。例如:目錄“src”下存在一個.c的源文件“foo.c”,我們的Makefile中不存在對目標“foo”的描述規則,或者當前目錄下就沒有默認的makefile文件,爲了編譯“foo.c”生成可執行的“foo”。只需要將“foo”作爲make的參數執行:“make foo”就可以實現編譯“foo”的目的。

make在執行時設置一個特殊變量“MAKECMDGOALS”,此變量記錄了命令行參數指定的終極目標列表,沒有通過參數指定終極目標時此變量爲空。注意:此變量僅用在特殊的場合(比如判斷),在Makeifle中不要對它進行重新定義!例如:

sources = foo.c bar.c    

ifneq ($(MAKECMDGOALS),clean)

include $(sources:.c=.d)

endif

 

例子中使用了變量“MAKECMDGOALS”來判斷命令行參數是否指定終極目標爲“clean”,如果不是才包含所有源文件對應的依賴關係描述文件。上例的功能是避免“make clean”時make試圖重建所有.d文件的過程。

這種方式主要用在以下幾個方面:

1.        對程序的一部分進行編譯,或者僅對某幾個程序進行編譯而不是完整編譯這個工程(也可以在命令行參數中明確給出原本的默認的終極目標,例如:make all,沒有人會說它是錯誤的。但是大家都會認爲多此一舉)。例如以下一個Makefile的片段,其中各個文件都有自己的描述規則:

 

.PHONY: all

all: size nm ld ar as

 

僅需要重建“size”文件時,執行“make size”就可以了。其它的程序就不會被重建。

2.        指定編譯或者創建那些正常編譯過程不能生成的文件(例如重建一個調試輸出文件、或者編譯一個調試版本的程序等),這些文件在Makefile中存在重建規則,但是它們沒有出現在默認終極目標目標的依賴中。

3.        指定執行一個由僞目標定義的若干條命令或者一個空目標文件。如絕大多數Makefile中都會包含一個“clean”僞目標,這個僞目標定義了刪除make過程生成的所有文件的命令,需要刪除這些文件時執行“make clean”就可以了。本節以下列出了一些典型的僞目標和空目標的名字。

部分標準的僞目標和空目標命名:

²       all

作爲Makefile的頂層目標,一般此目標作爲默認的終極目標。

²       clean

這個僞目標定義了一組命令,這些命令的功能是刪除所有由make創建的文件。

²       mostlyclean

和“clean”僞目標功能相似。區別在於它所定義的刪除命令不會全部刪除由make生成的文件。比如說不需要刪除某些庫文件。

²       distclean

²       realclean

²       clobber

同樣類似於僞目標“clean”,但它們所定義的刪除命令所刪除的文件更多。可以包含非make創建的文件。例如:編譯之前系統的配置文件、鏈接文件等。

²       install

將make成功創建的可執行文件拷貝到shell 環境變量“PATH”指定的某個目錄。典型的,應用可執行文件被拷貝到目錄“/usr/local/bin”,庫文件拷貝到目錄“/usr/local/lib”目錄下。

²       print

打印出所有被更改的源文件列表。

²       tar

創建一個tar文件(歸檔文件包)。

²       shar

創建一個源代碼的shell文檔(shar文件)。

²       dist

爲源文件創建發佈的壓縮包,可以使各種壓縮方式的發佈包。

²       TAGS

創建當前目錄下所有源文件的符號信息(“tags”)文件,這個文件可被vim使用。

²       check

²       test

對Makefile最後生成的文件進行檢查。

這些功能和目標的對照關係並不是GNU make規定的。你可以在Makefile中定義任何命名的僞目標。但是以上這些都被作約定,所有開源的工程中這些特殊的目標的命名都是按照這種約定來的。既然絕大多數程序員都遵循這種約定,自然我們也應該按照這種約定來做。否則在別人看來這樣Makefile只能算一個樣例,不能作爲正式版本。

9.3      替代命令的執行

書寫Makefile的目的就是爲了告訴make一個目標是否過期,以及如果重建一個過期的目標。但是在某些時候,我們並不希望真正更新那些已經過期的目標文件(比如:只是檢查更新目標的命令是否正確,或者察看那些目標需要更新)。要實現這樣的目的,可以使用一些特定的參數來限定make執行的動作。通過指定參數,替代make默認動作的執行。因此我們把這種方式稱爲:替代命令的執行。這些參數包括:

-n

--just-print

--dry-run

--recon

指定make執行空操作(不執行規則的命令),只打印出需要重建目標使用的命令(只打印過期的目標的重建命令),而不對目標進行重建。

-t

--touch

類似於shell下的“touch”命令的功能。更新所有目標文件的時間戳(對於過時的目標文件不進行內容更新,只更新時間戳)。

-q

--question

不執行任何命令並且不打印任何輸出信息,只檢查所指定的目標是否已經是最新的。如果是則返回0,否則返回1。使用“-q”(“--question”)的目的只是讓make返回給定(沒有指定則時終極目標)的目標是否是最新的。可以根據它的返回值來判斷是否須要真正的執行更新目標的動作。

-W FILE

--what-if= FILE

--assume-new= FILE

--new-file= FILE

這個參數需要指定一個文件名。通常是一個存在源文件。make將當前系統時間作爲這個文件的時間戳(假設這個文件被修改過,但不真正的更改文件本身的時間戳)。因此這個文件的時間戳被認爲最新的,在執行時依賴於這個文件的目標將會被重建。通過這種方式並結合“-n”參數,我們可以查看那些目標依賴於這個文件(修改這個文件以後執行make那些目標會被更新)。

通常“-W”參數和“-n”參數一同使用,可以在修改一個文件後來檢查修改會造成那些目標需要被更新,但並不執行更新的命令,只是打印命令。

“-W”和“-t”參數配合使用時,make將忽略其它規則的命令。只對依賴於“-W”指定文件的目標執行“touch”命令,在沒有使用“-s”時,可以看到那些文件執行了“touch”。需要說明的是,make在對文件執行“touch”時不是調用shell的命令,而是由make直接操作。

“-W”和“-q”參數配合使用時。由於將當前時間作爲指定文件的時間戳(目標文件相對於系統當前時間是過時的),所以make的返回狀態在沒有錯誤發生時爲1,存在錯誤時爲2。

注意:以上三個參數同時使用時可能會出現錯誤。

 

總結:

參數“-n”、“-t”和“-q”不影響之前帶“+”號和包含“$(MAKE)”的命令行的執行。就是說如果在規則的命令行中命令之前使用了“+”或者此命令行是遞歸地make調用時,無論是否使用了這三個參數之一,這些命令都得到執行。

“-W”參數有兩個特點:

1.        可以和“-n”或者“-q”參數配合使用來查看修改所帶來的影響(導致那些目標會被重建)。

2.        在不指定“-n”和“-q”參數、只使用“-W”指定一個文件時,可以模擬這個文件被修改的狀態。make就會重建依賴於此文件的所有目標。

另外“-p”和“-v”參數可以允許我們輸出Makefille被執行的過程信息,相信這一點在很多場合,特別是調試Makefile時非常有用(本章的最後一節有詳細討論,

9.4      防止特定文件重建

有時當修改了工程中的某一個文件後,並不希望重建那些依賴於這個文件的目標。比如說我們在給一個頭文件中加入了一個宏定義、或者一個增加的函數聲明,這些修改不會對已經編譯完成的程序產生任何影響。但在執行make時,因爲頭文件的改變會導致所有包含它的源文件被重新編譯,當然了終極目標肯定也會被重建(除非你的模塊時獨立的,或者你的Makefile的規則鏈的定義本身就存在缺陷)。這種情況時,爲了避免重新編譯整個工程,我們可以按照下邊的過程來處理:

第一種:

1.        使用“make”命令對所有需要更新的目標進行重建。保證修改某個文件之前所有的目標已經是最新的。

2.        編輯需要修改的那個源文件(修改的頭文件的內容不能對之前的編譯的程序有影響,比如:更改了頭文件中的宏定義。這樣會造成已經存在的程序和實現不相符!這裏所說的修改指:不改變已經存在的任何東西,除非你有特殊的要求)。

3.        使用“make -t”命令來改變已存在的所有的目標文件的時間戳,將其最後修改時間修改到當前時間。

第一種方式的現實基於這樣的前提:需要對未更改頭文件之前的編譯程序進行保存(有點像備份)。通常這種需求還是比較少見的。更多的情況是:修改這個頭文件之前已經修改了很多源文件或者其它的頭文件,並且也沒有執行make(缺少了上邊的第一步)。這種方式,有點像有計劃的修改。沒有普遍性,修改通常是不可預知的。因此這種方式在實際應用中幾乎沒有參考的意義。對於不可預知的修改所帶來的問題,我們需要另外一種方式。

第二種方式在很大程度上可以滿足我們的需求:

第二種:

1.        執行編譯,使用“make –o HEADERFILE”,“HEADERFILE”爲需要忽略更改的頭文件,防止那些依賴於這個頭文件的目標被重建。忽略多個頭文件的修改可使用多個“-o HEADERFILE”。這樣,頭文件“HEADERFILE”的修改就不會觸發依賴它的目標被重建(通過“-o”告訴make,這個頭文件的時間戳比它的依賴晚)。需要注意的是:“-o”參數的這種使用方式僅限於頭文件(.h文件),不能使用“-o”來指定源文件。

2.        執行“make -t”命令。

9.5      替換變量定義

執行make時,一個含有“=”的命令行參數“V=X”的含義是定義變量“V”的值爲“X”,並將這個比變量作爲make的參數。這種方式定義的變量會替代Makefile中的同名變量定義(如果存在,並且在Makefile中沒有使用指示符“override”對這個變量進行說明),這個過程被稱之命令行參數定義覆蓋普通變量定義。

通常用這種方式來傳遞一個公共變量給make。例如:在Makefile中,使用變量“CFLAGS”來指定編譯參數,在Makefile規則的命令一般都是這麼寫的:

 

cc -c $(CFLAGS) foo.c

 

這樣就可以通過改變“CFLAGS”的值來控制編譯選項。我們可以在Makefile中爲它指定值。例如:

 

CFLAGS=-g

 

當直接執行“make”時,編譯命令是“cc –c –g foo.c”。如果需要改變“CFLAGS”的定義,可以在命令行中指定這個變量的值,像這樣“make CFLAGS=’-g –O2’”,此時所執行的編譯命令將是“cc –c –g –O2 foo.c”(在參數中如果包含空格或者shell的特殊字符,則需要將參數放在引號中)。對變量“CFLAGS”定義追加的功能就是使用這種方式來實現的。

變量“CFLAGS”可以通過這種方式來實現,它是make的隱含變量之一。對於普通變量的定義,也可以通過這種方式來對它進行重新定義(覆蓋Makefile中的定義)、或者實現變量值的追加功能。

通過命令行定義變量時,也存在兩種風格的變量定義:遞歸展式定義和直接展開式定義。上例中使用遞歸展開式的定義(使用“=”),也可以是直接展開式的(使用“:=”)。除非在命令行定義的變量值中包含了對其他變量或者函數的引用,否則這兩種方式在此是等價的。

爲了防止命令行參數的變量定義覆蓋Makefile中的同名變量定義,可以在Makefile中使用指示符“override”聲明這個變量。這一點前邊的章節已經有詳細的討論!

9.6      使用make進行編譯測試

正常情況make在執行Makefile時,如果出現命令執行的錯誤,會立即放棄繼續執行並返回一個非0的狀態。就是說錯誤發生點之後的命令將不會被執行。一個錯誤的發生就表明了終極目標將不能被重建,make一旦檢查到錯誤就會立刻終止執行。

假如我們在修改了一些源文件之後重新編譯工程,當然了,我們所希望的是在某一個文件編譯出錯以後能夠繼續進行後續文件的編譯。直到最後出現鏈接錯誤時才退出。這樣的目的是爲了瞭解所修改的文件中那些文件沒有修改正確。在下一次編譯之前能夠對出現錯誤的所有文件進行改正。而不是編譯一次改正一個文件,或者改正一個文件再編譯一次。

我了實現我們這個目的,需要使用make的“-k”或者“--keep-going”命令行選項。這個參數的功能是告訴make當出現錯誤時繼續執行,直到最後出現致命錯誤(無法重建終極目標)才返回非0並退出。例如:當編譯一個.o目標文件時出現錯誤,如果使用“make -k”執行,make將不會在檢測到這個錯誤時退出(雖然已經知道終極目標是不可能會重建成功的);只是給出一個錯誤消息,make將繼續重建其它需要重建的目標文件;直到最後出現致命錯誤才退出。在沒有使用“-k”或者“—keep-going”時,make在檢測到錯誤時會立刻退出。

總之:在通常情況下,make的目的是重建終極目標。當它在執行過程中一旦發現無法重建終極目標,就立刻以非0狀態退出。當使用“-k”或者“--keep-going”參數時,執行的目的是爲了測試重建過程,需要發現存在的所有問題,以便在下一次make之前進行修正。這也是調試Makefile或者查找源文件錯誤的一種非常有效的手段。

9.7      make的命令行選項

本節羅列了make 所支持的命令行參數(這些參數可以通過man手冊查看):

-b

-m

忽略,提供其它版本make兼容性。

-B

--always-make

強制重建所有規則的目標,不根據規則的依賴描述決定是否重建目標文件。

-C DIR

--directory=DIR

在讀取Makefile之前,進入目錄“DIR”,就是切換工作目錄到“DIR”之後執行make。存在多個“-C”選項時,make的最終工作目錄是第一個目錄的相對路徑。如:“make –C / -C etc”等價於“make –C /etc”。一般此選項被用在遞歸地make調用中。

-d

make在執行過程中打印出所有的調試信息。包括:make認爲那些文件需要重建;那些文件需要比較它們的最後修改時間、比較的結果;重建目標所要執行的命令;使用的隱含規則等。使用“-d”選項我們可以看到make構造依賴關係鏈、重建目標過程的所有信息,它等效於“—debug=a”.

—debug[=OPTIONS]

make執行時輸出調試信息。可以使用“OPTIONS”控制調試信息級別。默認是“OPTIONS=b”,“OPTIONS”的可能值爲以下這些,首字母有效(all 和 aw等效)。

a(all)

輸出所有類型的調試信息,等效於“-d”選項。

b(basic)

輸出基本調試信息。包括:那些目標過期、是否重建成功過期目標文件。

v(verbose)

“basic”級別之上的輸出信息。包括:解析的makefile文件名,不需要重建文件等。此選項目默認打開“basic”級別的調試信息。

i(implicit)

輸出所有使用到的隱含規則描述。此選項目默認打開“basic”級別的調試信息。

j(jobs)

輸出所有執行命令的子進程,包括命令執行的PID等。

m(makefile)

也就是makefile,輸出make讀取makefile,更新makefile,執行makefile的信息。

-e

--environment-overrides

使用系統環境變量的定義覆蓋Makefile中的同名變量定義。

-f=FILE

--file= FILE

--makefile= FILE

指定“FILE”爲make執行的makefile文件。

-h

--help

打印幫助信息。

-i

--ignore-errors

執行過程中忽略規則命令執行的錯誤。

-I DIR

--include-dir=DIR

指定被包含makefile文件的搜索目錄。在Makefile中出現“include”另外一個文件時,將在“DIR”目錄下搜索。多個“-I”指定目錄時,搜索目錄按照指定順序進行。

-j  [JOBS]

--jobs[=JOBS]

指定可同時執行的命令數目。在沒有指定“-j”參數的情況下,執行的命令數目將是系統允許的最大可能數目。存在多個“-j”參數時,盡最後一個“-j”指定的數目(“JOBS”)有效。

-k

--keep-going

執行命令錯誤時不終止make的執行,make盡最大可能的執行所有的命令,直到出現致命錯誤才終止。

-l LOAD

--load-average[=LOAD]

—max-load[=LOAD]

告訴make當存在其它任務在執行時,如果系統負荷超過“LOAD”(浮點數表示的),不再啓動新任務。沒有指定“LOAD”的“-I”選項將取消之前“-I”指定的限制。

-n

--just-print

--dry-run

--recon

只打印出所要執行的命令,但不執行命令。

-o FILE

--old-file= FILE

--assume-old= FILE

指定文件“FILE”不需要重建,即使相對於它的依賴已經過期;同時也不重建依賴於此文件任何文件(目標文件)。注意:此參數不會通過變量“MAKEFLAGS”傳遞給子make進程。

-p

--print-data-base

命令執行之前,打印出make讀取的Makefile的所有數據(包括規則和變量的值),同時打印出make的版本信息。如果只需要打印這些數據信息(不執行命令)可以使用“make -qp”命令。查看make執行前的預設規則和變量,可使用命令“make –p -f /dev/null”。

-q

--question

稱爲“詢問模式”;不運行任何命令,並且無輸出。make只是返回一個查詢狀態。返回狀態爲0表示沒有目標需要重建,1表示存在需要重建的目標,2表示有錯誤發生。

-r

--no-builtin-rules

取消所有內嵌的隱含規則,不過你可以在Makefile中使用模式規則來定義規則。同時選項“-r”會取消所有支持後追規則的隱含後綴列表,同樣我們也可以在Makefile中使用“.SUFFIXES”定義我們自己的後綴規則。“-r”選項不會取消make內嵌的隱含變量。

-R

--no-builtin-variabes

取消make內嵌的隱含變量,不過我們可以在Makefile中明確定義某些變量。注意,“-R”選項同時打開“-r”選項。因爲沒有了隱含變量,隱含規則將失去意義(隱含規則是以內嵌的隱含變量爲基礎的)。

-s

--silent

--quiet

取消命令執行過程的打印。

-S

--no-keep-going

--stop

取消“-k”選項。在遞歸的make過程中子make通過“MAKEFLAGS”變量繼承了上層的命令行選項。我們可以在子make中使用“-S”選項取消上層傳遞的“-k”選項,或者取消系統環境變量“MAKEFLAGS”中的“-k”選項。

-t

—touch

和Linux的touch命令實現功能相同,更新所有目標文件的時間戳到當前系統時間。防止make對所有過時目標文件的重建。

-v

--version

查看make版本信息。

-w

--print-directory

在make進入一個目錄讀取Makefile之前打印工作目錄。這個選項可以幫助我們調試Makefile,跟蹤定位錯誤。使用“-C”選項時默認打開這個選項。參考本節前半部分“-C”選項的描述。

--no-print-directory

取消“-w”選項。可以是用在遞歸的make調用過程中,取消“-C”參數的默認打開“-w”功能。

-W FILE

--what-if= FILE

--new-file= FILE

--assume-file= FILE

設定文件“FILE”的時間戳爲當前時間,但不改變文件實際的最後修改時間。此選項主要是爲實現了對所有依賴於文件“FILE”的目標的強制重建。

--warn-undefined-variables

在發現Makefile中存在對沒有定義的變量進行引用時給出告警信息。此功能可以幫助我們調試一個存在多級套嵌變量引用的複雜Makefile。但是:我們建議在書寫Makefile時儘量避免超過三級以上的變量套嵌引用。

發佈了13 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章