Makefile 自動生成依賴
雖然以前對Makefile有個基本概念,但是真正到自己去寫一個哪怕是簡單的Makefile時也會遇到不少的麻煩。
現在我有如下文件 dList.h dList.c memory.c debug.c debug.h test.c aaron.h 其中包含關系如下:
aaron.h-->dList.h debug.h
dList.c-->aaron.h
debug.c-->aaron.h
test.c-->aaron.h
memory.c-->aaron.h
第一次寫Makefile如下:
OBJS := test.o debug.o memory.o dList.o
MACRO = DEBUGALL
CFLAGS+= -g -D$(MACRO)
CC = gcc
main: $(OBJS)
$(CC) $(OBJS) -D$(MACRO) $(CFLAGS) -o main
clean:
rm -f $(OBJS)
rm -f main
第一次看看似乎沒什么問題啊。真正運行發現這個Makefile沒有反應出.c文件對.h文件的依賴性,所以當.h文件發生變化時Makefile執行并不會發生變化。于是有了第二版:
OBJS := test.o debug.o memory.o dList.o
MACRO = DEBUGALL
CFLAGS+= -g -D$(MACRO)
CC = gcc
main: $(OBJS)
$(CC) $(OBJS) -D$(MACRO) $(CFLAGS) -o main
%o: aaron.h
clean:
rm -f $(OBJS)
rm -f main
這樣當aaron.h發生變化時,所有.o都會被更新,從而main也會被更新。但是問題還是有,aaron是依賴于dList.h, debug.h的,如果這兩個頭文件發生變化在這個Makefile里仍然不能使main重新編譯。繼續改造:
OBJS := test.o debug.o memory.o dList.o
MACRO = DEBUGALL
CFLAGS+= -g -D$(MACRO)
CC = gcc
main: $(OBJS)
$(CC) $(OBJS) -D$(MACRO) $(CFLAGS) -o main
%o: aaron.h dList.h debug.h
clean:
rm -f $(OBJS)
rm -f main
這下看上去能滿足我的要求。但是仔細一想就會發現如果我這個工程很大,靠手工去找到每個.c文件對應的所有頭文件然后來完成這個Makefile似乎是不太可能而且也很難維護。于是想著Makefile應該有機制能自動生成依賴關系吧。
于是找出寶典《GNU make中文手冊》搜索了下果然有自動生成依賴相關內容。主要是利用gcc 的編譯選項-M和-MM。不過讓我看了半天也沒有看明白其中的玄機。最后又重新翻看了改寶典眾多章節總算理解了。先把代碼貼出來:
MACRO = DEBUGALL
CFLAGS+= -g -w -D$(MACRO)
SOURCES = $(wildcard *.c)
OBJS := $(patsubst %.c, %.o,$(SOURCES))
CC = gcc
main: $(OBJS)
@echo "source files:" $(SOURCES)
@echo "object files:" $(OBJS)
$(CC) $(OBJS) -D$(MACRO) $(CFLAGS) -o main
sinclude $(SOURCES:.c=.d)
%d: %c
@echo "create depend"
$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ ,g' < $@.$$$$ > $@; \
$(RM) $@.$$$$
clean:
rm -rf $(OBJS)
rm -f main
下面一行一行來分析下這個Makefile.
前面兩行很簡單就是定義編譯變量和編譯選項。
SOURCES = $(wildcard *.c) 這句話意思是定義一個變量SOURCES,它的值包含當前目錄下所有.c文件。 在我的例子里我把這個值打印出來了就是dList.c memory.c test.c debug.c
$(wildcard PATTEN) 是Makefile內建的一個函數:
函數名稱:獲取匹配模式文件名函數—wildcard
函數功能:列出當前目錄下所有符合模式“PATTERN”格式的文件名。
返回值:空格分割的、存在當前目錄下的所有符合模式“PATTERN”的文件名。
函數說明:“PATTERN”使用shell可識別的通配符,包括“?”(單字符)、“*”(多字符)等
OBJS := $(patsubst %.c, %.o,$(SOURCES)) 這一行是定義了一個變量OBJS,它的值是將變量SOURCES里的內容以空格分開,將所有.c文件替換成.o. 在我的例子里打印出來就是dList.o memory.o test.o debug.o。
$(patsubst PATTEN, REPLACEMENT, TEXT)也是內建函數
函數名稱:模式替換函數—patsubst。
函數功能:搜索“TEXT”中以空格分開的單詞,將否符合模式“TATTERN”替換為“REPLACEMENT”
sinclude $(SOURCES:.c=.d) 這一行是非常關鍵的,它在當前Makefile里去include另外的Makefile. 這里“另外”的Makefile是將SOURCES變量里所有.c替換成.d。 在我的例子里就是dList.d memory.d test.d debug.d. 意思就是執行到這里
的時候先去依次執行dList.d memory.d test.d debug.d. 這里的.d文件就是包含了每個.c文件自動生成的對頭文件的依賴關系。這個依賴關系將由下面的%d:%c來完成。
%d: %c
此規則的含義是:所有的.d文件依賴于同名的.c文件。
第一行;使用c編譯器自自動生成依賴文件($<)的頭文件的依賴關系,并輸出成為一個臨時文件,“$$$$”表示當前進程號。如果$(CC)為GNU的c編譯工具,產生的依賴關系的規則中,依賴頭文件包括了所有的使用的系統頭文件和用戶定義的頭文件。如果需要生成的依賴描述文件不包含系統頭文件,可使用“-MM”代替“-M”。
第二行;使用sed處理第二行已產生的那個臨時文件并生成此規則的目標文件。經過這一行后test.d里內容如下:test.o: test.c aaron.h dList.h debug.h 其他.d里以此類推。
第三行;刪除臨時文件。
到這里基本的意義弄明白了,但是讓我不解的是%d: %c這個依賴的規則怎么能被執行到的?按照我的理解Makefile在執行時首先檢查終極目標main是否存在,如果不存在則建立(根據main的依賴規則),如果存在在需要查看
main的依賴文件是否存在并且是最新的,這我的例子里就是要看test.o dList.o memory.o debug.o是否存在且最新。這樣追下去是否沒有%d: %c什么事啊, .d文件也應該不存在或者說是空的。盡管我們include了.d文件,但是沒有依賴規則去執行它啊。后來仔細閱讀了
Makefile文件的重建才明白了。
Makefile如果由其它文件重建(這里我的Makefile include了所有.d文件,.d也可以看成是一個Makefile),Makefile在讀入所有其他makefile文件(.d)之后,首先將所讀取的每個makefile(.d)作為一個目標,尋找更新它們的規則。同樣
如果此目標不存在則根據依賴規則重新創建。在例子里其實.d文件開始并不存在,所以當Makefile在include這些.d文件時首先看.d存在不,不存在就要去尋找.d的依賴文件和規則。這里就找到了%d: %c從而創建出真正的.d文件。其實這里的關鍵點就是對于
include了理解,它是把include的文件首先當成一個目標,然后要去尋找其依賴文件和規則的,而不是我事先想象的簡單的把其他文件的內容包含過來。
到此,問題解決,基本達到預期。