前言
Makefile自動生成頭文件依賴是很常用的功能,本文的目的是想盡量詳細說明其中的原理和過程。
Makefile模板
首先給出一個本人在小項目中常用的Makefile模板,支持自動生成頭文件依賴。
CC = gcc CFLAGS = -Wall -O INCLUDEFLAGS = LDFLAGS = OBJS = seq.o TARGETS = test_seq .PHONY:all all : $(TARGETS) test_seq:test_seq.o $(OBJS) $(CC) -o $@ $^ $(LDFLAGS) %.o:%.c $(CC) -o $@ -c $< $(CFLAGS) $(INCLUDEFLAGS) %.d:%.c @set -e; rm -f $@; $(CC) -MM $< $(INCLUDEFLAGS) > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ -include $(OBJS:.o=.d) .PHONY:clean clean: rm -f $(TARGETS) *.o *.d *.d.*
基礎知識
在進行下一步之前,首先需要了解make的執行步驟:
- 讀入Makefile
- 讀入被include的其它Makefile
- 初始化Makefile中的變量
- 推導隱晦規則,並分析所有規則
- 為所有目標創建依賴關系鏈
- 根據依賴關系,決定哪些目標需要重新生成
- 執行生成命令
如何動態生成依賴關系?
從上面make的執行過程中可看出,要動態生成依賴關系,只能利用第2步讀入其它Makefile的機制。那么,我們是否可以先把生成的依賴關系保存到文件,然后再把該文件的內容包含進來?
答案是Yes! 只要利用include的機制。
include關鍵字是用於讀入其它Makefile文件。當該文件不存在時,make會尋找是否有生成它的規則,如果有,則執行其生成命令,然后再嘗試讀入。在include前加減號"-"可以上make忽略其產生的錯誤,並不輸出任何錯誤信息。
即是說,我們需要提供生成規則文件的規則。例如,我們可以這樣動態生成頭文件依賴關系:
seq.d : seq.c
@echo “seq.o seq.d : seq.c seq.h" > $@
-include seq.d
當make執行時,Makefile中的內容將是這樣子(指內存上的數據):
seq.d : seq.c
@echo “seq.o seq.d : seq.c seq.h" > $@
seq.o seq.d : seq.c seq.h
特別注意的是,由於對seq.c和seq.h的修改需要更新seq.d的內容(因為依賴關系可能已變化),因此seq.d也要在依賴關系的目標列表中。
自動生成頭文件依賴
基於上面的例子,現在可以開始討論如何自動生成頭文件依賴。
自動生成依賴關系
大多數c/c++編譯器提供了-M選項,可自動尋找源文件依賴的頭文件,並生成依賴規則。對於gcc,需要使用-MM選項,否則它會把系統依賴的頭文件也包含進來。例如執行下面一個命令:
gcc -MM seq.c
將輸出:
seq.o : seq.c seq.h
但我們需要結果是seq.d也要包含在目標列表中,所以還需要對它進行文本處理。因此,上面的例子可改為:
seq.d : seq.c
@set -e; \
gcc -MM $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
-include seq.d
生成規則中的執行命令解釋
第一個命令@set -e
。@關鍵字告訴make不輸出該行命令;set -e的作用是,當后面的命令的返回值非0時,立即退出。
那么為什么要把幾個命令寫在”同一行“(是對於make來說,因為\的作用就是連接行),並用分號隔開每個命令?因為在Makefile這樣做才能使上一個命令作用於下一個命令。這里是想要set -e作用於后面的命令。
第二個命令gcc -MM $< > $@.$$$$
, 作用是根據源文件生成依賴關系,並保存到臨時文件中。內建變量$<
的值為第一個依賴文件(那seq.c),$$$$
為字符串"$$"
,由於makefile中所有的$字符都是特殊字符(即使在單引號之中!),要得到普通字符$,需要用$$
來轉義; 而$$
是shell的特殊變量,它的值為當前進程號;使用進程號為后綴的名稱創建臨時文件,是shell編程常用做法,這樣可保證文件唯一性。
第三個命令作用是將目標文件加入依賴關系的目錄列表中,並保存到目標文件。關於正則表達式部分就不說了,唯一要注意的是內建變量$*
,$*
的值為第一個依賴文件去掉后綴的名稱(這里即是seq)。
第四個命令是將該臨時文件刪除。
如果把內建變量都替換成其值后,實際內容是這樣子:
seq.d : seq.c
@set -e; \
gcc -MM seq.c > seq.d.$$$$; \
sed 's,\(seq\)\.o[ :]*,\1.o seq.d : ,g' < seq.d.$$$$ > seq.d; \
rm -f seq.d.$$$$
-include seq.d
Makefile的模式匹配
最后,再把Makefile的模式匹配應用上,就完成自動生成頭文件依賴功能了:
%.d : %.c
@set -e; \
gcc -MM $@ > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
-include seq.d
參考資料
<跟我一起寫Makefile> by 陳晧
GNU make官方文檔 http://www.gnu.org/software/make/manual/make.html