《GNU make》學習筆記(四)---規則的命令
4.1 命令回顯
通常,make在執行命令列之前會把要執行的命令列進行輸出。我們稱之為“回顯”,就好像我們輸入命令執行一樣。
如果要執行的命令列以字元“@”開始,則make在執行時這個命令就不會被回顯。典型的用法是我們在使用“echo”命令輸出一些資訊時。 例如:
@echo 正在編譯XXX模組......
當make執行時,會輸出“正在編譯XXX模組......”字串,但不會輸出命令,如果沒有“@”,那麼,make將輸出:
echo 正在編譯XXX模組......
正在編譯XXX模組......
如果使用make的命令列引數“-n”或“--just-print”,那麼make執行時只顯示所要執行的命令,但不會真正的去執行這些命令。這個選項對於我們除錯Makefile非常有用。
而make引數“-s”或“--slient”則是禁止所有執行命令的顯示,就好像所有的命令列均使用“@”開始一樣。在Makefile中使用沒有依賴的特殊目標“.SILENT”也可以禁止命令的回顯,但是它的缺點是不如“@”靈活。因此我們在書寫Makefile時,推薦使用“@”來控制命令的回顯。
介紹一下.SILENT:.出現在目標“.SILENT”的依賴列表中的檔案,make在建立這些檔案時,不打印出重建此檔案所執行的命令。同樣,給目標“.SILENT”指定命令列是沒有意義的。沒有任何依賴檔案的目標“.SILENT”告訴make在執行過程中不列印任何執行的命令。
4.2 命令的執行
規則中,當目標需要被重建時。此規則所定義的命令將會被執行,如果是多行命令,那麼make就為每一行命令使用一個獨立的子shell去執行。因此,多行命令之間的執行是相互獨立的,相互之間不存在依賴。
而在Makefile中書寫在同一行中的多個命令屬於一個完整的shell命令列,書寫在獨立行的一條命令是一個獨立的shell命令列。所以需要注意:在一個規則的命令中,命令列“cd”改變目錄不會對其後的命令的執行產生影響。就是說其後的命令執行的工作目錄不會是之前使用“cd”進入的那個目錄。
這解釋了我之前的一個疑問:為什麼Makefile裡有cd,但是Make執行完之後還在Makefile所在的目錄。這就是因為每一行命令都是獨立的。
4.3 併發執行命令
GUN make可以同時執行多條命令。通常情況下,一個時刻只有一個命令在執行,下一個命令在當前命令執行完成之後才能夠被執行。不過可以通過make的命令列選項“-j”或者“--job”來告訴make在同一時刻可以允許多條命令同時被執行。
如果選項“-j”之後存在一個整數,其含義是告訴make在同一時刻可允許同時執行命令的數目。這個數字被稱為“job slots”。當“-j”選項之後沒有出現一個數字時,那麼同一時刻執行的命令數目沒有要求。
並行執行命令所帶來的問題是顯而易見:
- 同一時刻的多個被執行的命令同時輸出,造成輸出到終端的資訊的交替,顯得凌亂
- 在同一時刻可能會存在多個命令執行程序讀取標準輸入,但是對於標註輸入來裝置來說,在同一時刻只能有一個程序訪問輸入裝置,因此當同一時刻存在多個執行命令的程序需要讀取標準輸入流時其它的將會出輸入流無效導致致命錯誤(通常此程序會得到作業系統的管道破裂訊號而被終止)。
- 會導致make的遞迴調用出現問題
4.4 命令執行的錯誤
規則中的命令在執行結束後,make會檢測命令執行的返回狀態,如果返回成功,那麼就在另外一個子shell下執行下一條命令。如果一個規則中的某一個命令出錯(返回狀態非0),make就會放棄對當前規則的執行,也有可能會終止所有規則的執行。
在一些情況下,規則中的一個命令的執行失敗並不代表規則執行的錯誤。例如我們使用“mkdir”命令來確保存在一個目錄。當此目錄不存在使我們就建立這個目錄,當目錄存在時那麼“mkdir”就會執行失敗。其實我們並不希望mkdir在執行失敗後終止規則的執行。為了忽略一些無關命令執行失敗的情況,我們可以在命令之前加一個減號“-”(在[Tab]字元之後),來告訴make忽略此命令的執行失敗。命令中的“-”號會在在shell解析並執行此命令之前被去掉,shell所解釋的只是純粹的命令,“-”字元是由make來處理的。
在執行make時,如果使用命令列選項“-i”或者“—ignore-errors”, make將會忽略所有規則中命令執行執行的錯誤。
沒有依賴的特殊目標“.IGNORE”在Makefile中有同樣的效果。.IGNORE”的方式已經很少使用,因為它沒有在命令列之前使用“-”字元方式靈活。
當使用make的“-i”選項或者使用“-”字元來忽略命令執行錯誤時,make始終會把命令的執行結果作為成功來對待。但會提示錯誤資訊,同時提示這個錯誤被忽略。
當沒有使用上述三種方式來通知make忽略命令的執行錯誤時,而在錯誤發生時,就意味著定義這個命令的規則的目標不能被正確重建,同樣,和此目標相關的其它目標也不會被正確重建。因此,由於先決條件不能建立,後續的命令將不會執行。在發生這樣情況時,一般make會立刻退出並返回一個非0狀態,表示執行失敗。像對待命令執行的錯誤一樣,我們可以使用make的命令列選項“-k”或者“--keep-going”來通知make,當出現錯誤時不立即退出,而是繼續後續命令的執行。直到無法繼續執行命令時才異常退出。。例如:使用“-k”引數,在重建一個.o檔案目標時出現錯誤,make不會立即退出。雖然make已經知道因為這個錯誤而無法完成終極目標的重建,但還是繼續完成其它後續的依賴檔案的重建。直到執行最後連結時才錯誤退出。
一般“-k”引數在實際中應用主要在:當同時修改了工程中的多個檔案後,“-k”引數可以幫助我們確認對那些檔案的修改是正確的(可以被編譯),那些檔案的修改是不不正確的(不能正確編譯)。例如我們修改了工程中的20個原始檔,修改完成之後使用“-k”引數來進行make,它可以一次性找出修改的20個檔案中哪些是不能被編譯的。
通常情況下,執行失敗的命令一旦改變了它所在規則的目標檔案,則這個改變了的目標可能不是一個被正確重建的檔案。但是這個檔案的時間戳已經被更新過了(這種情況也會發生在使用一個訊號來強制中止命令執行的時候)。因此在下一次執行make時,由於時間戳更新它不會被再次重建。因此終極目標的重建很難保證是正確的。為了避免這種錯誤的出現,應該在一次make執行失敗之後使用“make clean”來清除已經重建的所有目標,之後再執行make。
我們也可以讓make自動完成這個動作,實現這個目的我們只需要Makefile中定義特殊目標“.DELETE_ON_ERROR”。
推薦的做法是:在make執行失敗時,修改錯誤之後執行make之前,使用“make clean”明確的刪除第一次錯誤重建的所有目標。
本節小結:雖然make提供了命令列選項來忽略命令執行的錯誤,建議對於此選項謹慎使用。對於一些需要忽略的錯誤,可以用其他方式代替,例如刪除命令我們可以這樣寫: “$(RM)”或者“rm -f”,建立目錄的命令可以這樣寫:“mkdir –p ”等等。
4.5 中斷make的執行
make在執行命令時如果收到一個致命訊號(結束make),make將會刪除命令重建的規則目標檔案。其依據是此目標檔案的當前時間戳是否和make開始時的時間戳相同。
刪除這個目標檔案的目的是為了確保下一次make時目標檔案能夠被正確重建。假設正在編譯檔案是鍵入“Ctrl-c”,在這時編譯器已經開始寫檔案“foo.o”,但是“Ctrl-c”產生的訊號關閉了編譯器。這種情況時檔案“foo.o”可能是不完整的,但這個內容不完整的“foo.o”檔案的時間戳比源程式‘foo.c’的時間戳新。如果在make收到終止訊號後不刪除檔案“foo.o”而直接退出,那麼下次執行make時make將認為該檔案已是最新而不會去重建檔案它。最後在連結程式生成終極目標時可能由於某一個.o檔案的不完整,導致出現一些奇怪的令人難以理解的錯誤資訊。
同時,我們可以在Makefile中將一個目標檔案作為特殊目標“.PRECIOUS”的依賴,這樣可以防止make在重建這個目標時異常終止時對這個目標檔案的刪除動作。詳見《Make的特殊目標》中.PRECIOUS目標。
4.6 make的遞迴執行
make的遞迴呼叫指的是:在Makefile中使用“make”作為一個命令來執行本身或者其它makefile檔案。遞迴呼叫在一個村在有多級子目錄的專案中非常有用。例如,當前目錄下存在一個“subdir”子目錄,這個子目錄中有描述這個目錄編譯規則的makefile檔案,在執行make時需要從上層目錄(當前目錄)開始並完成它所有子目錄的編譯。那麼在當前目錄下可以使用這樣一個規則來實現對它的子目錄的編譯:
subsystem:
cd subdir && $(MAKE)
其等價於規則:
subsystem:
$(MAKE) -C subdir
4.6.1 變數MAKE
在使用make的遞迴呼叫時,在Makefile中規則的命令列中應該使用變數“MAKE”來代替直接使用“make”。就像上面的例子一樣。
變數“MAKE”的值是“make”程式的檔名。如果其值為“/bin/make”那麼上邊規則的命令就為“cd subdir && /bin/make”。這樣做的好處是:當我們使用一個其它版本的make程式時,可以保證最上層使用的make程式和其子目錄下執行的make保持一致。
另外使用此變數的另外一個特點是:當規則命令列中變數MAKE是,它可以改變make的“-t”(“--touch”),“-n”(“--just-print”)和“-q”(“--question”)命令列選項的效果。它所實現的功能和在規則中命令列首使用字元“+”的效果相同。
4.6.2 變數和遞迴
在make的遞迴執行過程中,上層make可以明確指定將一些變數的定義通過環境變數的方式傳遞給子make過程。但是對於沒有明確指定需要傳遞的變數,上層make不會將其傳遞給子make。
如果子make過程所執行Makefile中存在同名變數定義,則上層傳遞的變數定義不會覆蓋子Makefile中定義的值。就是說如果上層make傳遞的變數和子make所執行的Makefile中存在重複的變數定義,則以子Makefile中的變數定義為準。除非使用make的“-e”選項。
當上層make過程要將所執行的Makefile中的變數傳遞給子make過程時,需要明確地指出,在GNU make中,實現此功能的指示符是“export”。當一個變數使用“export”進行聲明後,變數和變數的值將被加入到當前工作的環境變數中,以後make所執行的所有規則的命令都可以使用這個變數。
除了“export”指定的變數,上層make還將那些已經初始化的環境變數(在執行make之前已經存在的環境變數)和使用命令列指定的變數(如命令“make CFLAGS +=-g”或者“make –e CFLAGS +=-g”中的變數CFLAGS)傳遞給子make程式。
通常變數由字元、數字和下劃線組成。需要注意的是:有些shell不能處理那些名字中包含(除字母、數字、下劃線以外)其他字元的變數。
兩個特殊的變數“SHELL”和“MAKEFLAGS”,除非使用指示符“unexport”對它們進行宣告,否則在整個make的執行過程中它們會始終被自動的傳遞給子make。
"export"或者“unexport”的使用格式:
export VARIABLE ...
另外,一個不帶任何引數的指示符“export”指示符含義是將此Makefile中定義的所有變數傳遞給子make過程。使用“export”將所有定義的變數傳遞給子Makefile時,那些名字中包含其它字元(除字母、數字和下劃線以外的字元)的變數可能不會被傳遞給子make,對這類特殊命名的變數傳遞需要明確的使用“export”指示符對它進行宣告。
“unexport”指示符來禁止一個變數的向下傳遞。這一動作也是現行版本make所預設的,因此我們就沒有必要在上層的Makefile中使用它。
在多級遞迴呼叫的make執行過程中。變數“MAKELEVEL”代表了呼叫的深度。在make一級級的執行過程中變數“MAKELEVEL”的值不斷的發生變化,通過它的值我們可以瞭解當前make遞迴呼叫的深度。最上一級時“MAKELEVEL”的值為“0”、下一級時為“1”、再下一級為“2”……
4.6.3 命令列選項和遞迴
在make的遞迴執行過程中,最上層(可以稱之為主控)make的命令列選項“-k”、“-s”等被自動的通過環境變數“MAKEFLAGS”傳遞給子make程序。同樣,在執行make時命令列中給定了一個變數的定義(如“make CFLAGS+=-g”),此變數和它的值(CFLAGS+=-g)也會藉助環境變數“MAKEFLAGS”傳遞給子make程序。
需要注意的是有幾個特殊的命令列選項例外,分別是:“-C”、“-f”、“-o”和“-W”,這些命令列選項不會被賦值給變數“MAKEFLAGS”。
執行多級的make呼叫時,如果不希望“MAKEFLAGS”的值傳遞給子make,就需要在執行子make時對它重新進行賦值。例如:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
此規則取消了子make執行式的命令列選項(將變數的值賦為空)。
在執行make的同時可以通過命令列來定義一個變數,像上例中的那樣;。其實真正的命令列中的變數定義 是通過另外一個變數“MAKEOVRRIDES”來記錄的,變數“MAKEFLAGS”引用此變數,因而命令列中的變數定義就可以被記錄在環境變數“MAKEFLAGS”中被傳遞下去。當不希望將上層make在命令列中定義的變數傳遞給子make時,就可以在上層Makefile中把“MAKEOVERRIDES”賦空來實現(MAKEOVERRIDES=)。這種方式一般很少使用,建議非萬不得已您還是最好不使用這種方式。
另外一些系統中對環境變數的長度存在一個上限,因此當“MAKEFLAGS”的值超過一定的數目時,執行過程出現了類似“Arg list too long”的錯誤提示。
4.6.4 -w選項
在多級make遞迴呼叫過程中,選項“-w”或者“--print-directory”可以讓make在開始編譯一個目錄之前和完成此目錄的編譯之後給出相應的提示資訊,方便開發人員能夠跟蹤make的執行過程。例如,在目錄“/u/gnu/make”目錄下執行“make -w”,將會看到如下的一些資訊:
在開始執行之前我們將看到:
make: Entering directory `/u/gnu/make'.
而在完成之後我們同樣將會看到:
make: Leaving directory `/u/gnu/make'.
通常,在主控Makefile中當如果使用“-C”引數來為make指定一個目錄或者使用“cd”進入一個目錄時,“-w”選項會被自動開啟。例如,在主Makefile中有:
test:
$(MAKE) -C test
執行時會提示:make[1]: Entering directory `/home/summer/code/make/test'
主控make可以使用選項“-s”(“--slient”)來禁止此選項的自動開啟。另外,make的命令列選項“--no-print-directory”,將禁止所有關於目錄資訊的列印。