1. 程式人生 > >Makefile偽目標

Makefile偽目標

而是 一次 我們 避免 並行 重名 理解 目標 完整

  本節我們討論一個Makefile中的一個重要的特殊目標:偽目標。

偽目標是這樣一個目標:它不代表一個真正的文件名,在執行make時可以指定這個目標來執行其所在規則定義的命令,有時我們也可以將一個偽目標稱為標簽。使用偽目標有兩點原因:

  1. 避免在我們的Makefile中定義的只執行命令的的目標(此目標的目的為了執行執行一系列命令,而不需要創建這個目標)和工作目錄下的實際文件出現名字沖突。

  2. 提高執行make時的效率,特別是對於一個大型的工程來說,編譯的效率也許你同樣關心。

以下就這兩個問題我們進行分析討論:

  1. 如果我們需要書寫這樣一個規則:規則所定義的命令不是去創建目標文件,而是使用make指定具體的目標來執一些特定的命令。像下邊那樣:

clean:

rm *.o temp

  規則中“rm”不是創建文件“clean”的命令,只是刪除當前目錄下的所有.o文件和temp文件。在工作目錄下不存在“clean”這個文件時,我們輸入“make clean”後,“rm *.o temp”總會被執行。這是我們的初衷。

但當前工作目錄下存在文件“clean”時情況就不一樣了,在我們輸入“make clean”時。規則沒有依賴文件,所以目標被認為是最新的而不去執行規則作定義的命令,命令“rm”將不會被執行。這並不是我們的初衷。為了避免這個問題,我們可以將目標“clean”明確的聲明為偽目標。將一個目標聲明為偽目標需要將它作為特殊目標.PHONY”的依賴。如下:

.PHONY : clean

  這樣目標“clean”就是一個偽目標,無論當前目錄下是否存在“clean”這個文件。我們輸入“make clean”之後。“rm”命令都會被執行。而且,當一個目標被聲明為偽目標後,make在執行此規則時不會試圖去查找隱含規則來創建這個目標。這樣也提高了make的執行效率,同時我們也不用擔心由於目標和文件名重名而使我們的期望失敗。在書寫偽目標規則時,首先需要聲明目標是一個偽目標,之後才是偽目標的規則定義。目標“clean”書寫格式應該如下:

.PHONY: clean

clean:

rm *.o temp

  2. 偽目標的另外一使用場合在make的並行和遞歸執行過程中。此情況下一般存在一個變量,其定義為所有需要make的子目錄。對多個目錄進行make的實現方式可以在一個規則中可以使用shell的循環來完成。如下:

SUBDIRS = foo bar baz

subdirs:

for dir in $(SUBDIRS); do \

$(MAKE) -C $$dir; \

done

  但這種實現方法存在以下幾個問題。1. 當子目錄執行make出現錯誤時,make不會退出。就是說,在對某一個目錄執行make失敗以後,會繼續對其他的目錄進行make。在最終執行失敗的情況下,我們很難根據錯誤的提示定位出具體是是那個目錄下的Makefile出現錯誤。這給問題定位造成了很大的困難。為了避免這樣的問題,我們可以在命令行部分加入錯誤的監測,在命令執行錯誤後make退出。不幸的是,如果在執行make時使用了“-k”選項,此方式將失效。2. 另外一個問題就是使用這種shell的循環方式時,沒有用到make對目錄的並行處理功能,因為規則的命令是一條完整的shell命令,不能被並行的執行。

我們可以通過偽目標方式來克服以上實現方式所存在的兩個問題。

SUBDIRS = foo bar baz

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):

$(MAKE) -C $@

foo: baz

  上邊的實現中使用了一個沒有命令行的規則“foo: baz”,用來限制子目錄的make順序。此規則的含義時在處理“foo”目錄之前,需要等待“baz”目錄處理完成。在書寫一個並行執行make的Makefile時,目錄的處理順序是需要特別註意的。

  一般情況下,一個偽目標不作為一個另外一個目標文件的依賴。這是因為當一個目標文件的依賴包含偽目標時,每一次在執行這個規則時偽目標所定義的命令都會被執行(因為它是規則的依賴,重建規則目標文件時需要首先重建它的依賴)。當偽目標沒有作為任何目標(此目標是一個可被創建或者已存在的文件)的依賴時,我們只能通過make的命令行選項明確指定這個偽目標,來執行它所定義的命令。例如我們的“make clean”。

  Makefile中,偽目標可以有自己的依賴。在一個目錄下如果需要創建多個可執行程序,我們可以將所有程序的重建規則在一個Makefile中描述。因為Makefile中第一個目標是“終極目標”,約定的做法是使用一個稱為“all”的偽目標來作為終極目標,它的依賴文件就是那些需要創建的程序。下邊就是一個例子:

#sample Makefile

all : prog1 prog2 prog3

.PHONY : all

prog1 : prog1.o utils.o

cc -o prog1 prog1.o utils.o

prog2 : prog2.o

cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o

cc -o prog3 prog3.o sort.o utils.o

  執行make時,目標“all”被作為終極目標。為了完成對它的更新,make會創建(不存在)或者重建(已存在)目標“all”的所有依賴文件(prog1、prog2和prog3)。當需要單獨更新某一個程序時,我們可以通過make的命令行選項來明確指定需要重建的程序。(例如: “make prog1”)。 當一個偽目標作為另外一個偽目標依賴時,make將其作為另外一個偽目標的子例程來處理(可以這樣理解:其作為另外一個偽目標的必須執行的部分,就行C語言中的函數調用一樣)。下邊的例子就是這種用法:

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff

rm program

cleanobj :

rm *.o

cleandiff :

rm *.diff

  “cleanobj”和“cleandiff”這兩個偽目標有點像“子程序”的意思(執行目標“clearall時會觸發它們所定義的命令被執行”)。我們可以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達到清除不同種類文件的目的。例子首先通過特殊目標“.PHONY”聲明了多個偽目標,它們之間使用空各分割,之後才是各個偽目標的規則定義。

說明:

  通常在清除文件的偽目標所定義的命令中“rm”使用選項“–f”(--force)來防止在缺少刪除文件時出錯並退出,使“make clean”過程失敗。也可以在“rm”之前加上“-”來防止“rm”錯誤退出,這種方式時make會提示錯誤信息但不會退出。為了不看到這些討厭的信息,需要使用上述的第一種方式。

  另外make存在一個內嵌隱含變量“RM”,它被定義為:“RM = rm –f”。因此在書寫“clean”規則的命令行時可以使用變量“$(RM)”來代替“rm”,這樣可以免出現一些不必要的麻煩!這是我們推薦的用法。

Makefile偽目標