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偽目標