后面會介紹gcc獲得源文件依賴的方法,gcc這個功能就是為make而存在的。我們采用gcc的-MM選項結合sed命令。使用sed進行替換的目的是為了在目標名前加上“objs/”前綴。gcc的-E選項,預處理。在生成依賴關系時,其實并不需要gcc編譯源文件,只要預處理就可以獲得依賴關系了。通過-E選項,可以避免生成依賴關系時gcc發出警告,以及提高依賴關系的生成效率。
現在,已經找到自動生成依賴關系的方法了,那么如何將其整合到我們complicated項目的Makefile中呢?自動生成的依賴信息不能直接出現在Makefile中,因為不能動態地改變Makefile中的內容,此時我們需要通過創建依賴關系文件的方式。假設依賴關系的文件以“.dep”結尾,因此我們新創建一個deps文件,用來存放依賴關系文件信息。
Makefile如下:
1 .PHONY: all clean 2 3 MKDIR = mkdir 4 RM = rm 5 RMFLAGS = -rf 6 7 CC=gcc 8 9 DIR_OBJS=objs 10 DIR_EXES=exes 11 DIR_DEPS=deps 12 13 DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) 14 EXE=complicated 15 EXE:=$(addprefix $(DIR_EXES)/,$(EXE)) 16 SRCS=$(wildcard *.c) 17 OBJS=$(SRCS:.c=.o) 18 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS)) 19 DEPS=$(SRCS:.c=.dep) 20 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS)) 21 22 all:$(DIRS) $(DEPS) $(EXE) 23 $(DIRS): 24 $(MKDIR) $@ 25 $(EXE):$(OBJS) 26 $(CC) -o $@ $^ 27 $(DIR_OBJS)/%.o:%.c 28 $(CC) -o $@ -c $^ 29 $(DIR_DEPS)/%.dep:%.c 30 @echo "Creating $@ ..." 31 @set -e;\ 32 $(RM) $(RMFLAGS) $@.tmp;\ 33 $(CC) -E -MM $^ >$@.tmp;\ 34 sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\ 35 $(RM) $(RMFLAGS) $@.tmp 36 clean: 37 $(RM) $(RMFLAGS) $(DIRS)
(這個Makefile廢了不少力氣才想明白。。。)
和之前的complicated項目的Makefile相比:
1,增加了deps文件夾
2,刪除了目標文件創建規則中的foo.h依賴,并將規則中的$<變回了$^
3,增加了了DEPS變量用于存放文件
4,為all目標增加了$(DEPS)
5,增加了一個用于創建依賴關系問價你的規則。在這個規則中,使用了gcc的-E和-MM選項來獲取依賴關系。在生成最終的依賴關系文件之前,使用了一個由$@.tmp表示的臨時文件,且在依賴文件生成以后將其刪除。set -e的作用是告訴shell,在生成依賴關系文件的過程中如果出現任何錯誤就直接退出。shell異常退出的最終表現就是make會告訴我們出錯了,從而停止后續的make工作。如果不設置這一行,當構建依賴出錯時,make還會繼續后面的工作并最終出錯,這并不是我們希望看到的。讀者可以測試故意在源文件或者頭文件中植入錯誤并去掉set -e選項觀察make的行為和加上set -e有上面不同。
這里還有幾個知識點需要補充。
1.對于規則中的每一條命令,make都是在一個新的shell上運行它的。
2.如果希望多個命令在同一個shell中運行,可以用“;”將這些命令連起來。
3.當命令很長時,可以用“\”將一個命令書寫成多行。
為了更好的理解第一點,我們做一個實驗。現假設需要創建一個test目錄,然后在這個test目錄下再創建一個subtest子目錄。編寫Makefile如下:
?
1 .PHONY:all 2 all: 3 @mkdir test 4 @cd test 5 @mkdir subtest
?
可以看到test和subtest是同級目錄并非父子目錄,然后用上面提到的知識點更改Makefile:
1 .PHONY:all 2 all: 3 @mkdir test;\ 4 cd test;\ 5 mkdir subtest
?
這樣就可以達到目的了。不過你可能會想,為什么這里后面的cd和最后一個mkdir不需要在前面加上@呢?那么我們加上試試呢?
?
如果使用了分號“ ;”,表示命令在同一個shell中運行,而且使用“ \”鏈接一條命令,既然是一條命令,自然不能夠識別后面的@cd或者@mkdir,因為最開始的mkdir使用@,讓終端不顯示執行的指令,后面的cd和mkdir是在前面操作的情況下進行的?,此時,直接使用命令即可。
還有一個需要注意的地方:
如同
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;
3.命令與選項
sed命令告訴sed如何處理由地址指定的各輸入行,如果沒有指定地址則處理所有的輸入行。
此處sed引用此博客, 參考鏈接:http://www.cnblogs.com/edwardlost/archive/2010/09/17/1829145.html
3.1 sed命令
?命令 | ?功能 |
?a\ | ?在當前行后添加一行或多行。多行時除最后一行外,每行末尾需用“\”續行 |
?c\ | ?用此符號后的新文本替換當前行中的文本。多行時除最后一行外,每行末尾需用"\"續行 |
?i\ | ?在當前行之前插入文本。多行時除最后一行外,每行末尾需用"\"續行 |
?d | ?刪除行 |
?h | ?把模式空間里的內容復制到暫存緩沖區 |
?H | ?把模式空間里的內容追加到暫存緩沖區 |
?g | ?把暫存緩沖區里的內容復制到模式空間,覆蓋原有的內容 |
?G | ?把暫存緩沖區的內容追加到模式空間里,追加在原有內容的后面 |
?l | ?列出非打印字符 |
?p | ?打印行 |
?n | ?讀入下一輸入行,并從下一條命令而不是第一條命令開始對其的處理 |
?q | ?結束或退出sed |
?r | ?從文件中讀取輸入行 |
?! | ?對所選行以外的所有行應用命令 |
?s | ?用一個字符串替換另一個 |
?g | ?在行內進行全局替換 |
? | ? |
?w | ?將所選的行寫入文件 |
?x | ?交換暫存緩沖區與模式空間的內容 |
?y | ?將字符替換為另一字符(不能對正則表達式使用y命令) |
?
3.2 sed選項
?選項 | ?功能 |
?-e | ?進行多項編輯,即對輸入行應用多條sed命令時使用 |
?-n | ?取消默認的輸出 |
?-f | ?指定sed腳本的文件名 |
?
?元字符 | ?功能 | ?示例 |
?^ | ?行首定位符 | ?/^my/? 匹配所有以my開頭的行 |
?$ | ?行尾定位符 | ?/my$/? 匹配所有以my結尾的行 |
?. | ?匹配除換行符以外的單個字符 | ?/m..y/? 匹配包含字母m,后跟兩個任意字符,再跟字母y的行 |
?* | ?匹配零個或多個前導字符 | ?/my*/? 匹配包含字母m,后跟零個或多個y字母的行 |
?[] | ?匹配指定字符組內的任一字符 | ?/[Mm]y/? 匹配包含My或my的行 |
?[^] | ?匹配不在指定字符組內的任一字符 | ?/[^Mm]y/? 匹配包含y,但y之前的那個字符不是M或m的行 |
?\(..\) | ?保存已匹配的字符 | ?1,20s/\(you\)self/\1r/? 標記元字符之間的模式,并將其保存為標簽1,之后可以使用\1來引用它。最多可以定義9個標簽,從左邊開始編號,最左邊的是第一個。此例中,對第1到第20行進行處理,you被保存為標簽1,如果發現youself,則替換為your。 |
?& | ?保存查找串以便在替換串中引用 | ?s/my/**&**/??符號&代表查找串。my將被替換為**my** |
?\< | ?詞首定位符 | ?/\<my/? 匹配包含以my開頭的單詞的行 |
?\> | ?詞尾定位符 | ?/my\>/? 匹配包含以my結尾的單詞的行 |
?x\{m\} | ?連續m個x | ?/9\{5\}/ 匹配包含連續5個9的行 |
?x\{m,\} | ?至少m個x | ?/9\{5,\}/? 匹配包含至少連續5個9的行 |
?x\{m,n\} | ?至少m個,但不超過n個x | ?/9\{5,7\}/? 匹配包含連續5到7個9的行 |
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;
現在來分解這個復雜表達式,首先,sed s表示我們想用一個字符串替換另一個字符串,這也是我們使用sed的原因,它的s命令就
可以達到這個效果。
's,\(.*\)\.o[:]*,objs/\1.o:,g'第一次分解,此時需要知道,單引號是一對的,即s前面的'和g后面的'是一個整體單引號,
這也是sed命令的基礎,至于單引號和雙引號有什么區別,可百度谷歌或者必應。(但是我之前測試的單引號和雙引號并不是我搜索所顯示的那樣,后面再試試吧)
繼續分解,s,中s是替換字符串的意思,這個在上面的表格中可以查詢到,逗號,表示模式分隔符,在這種有/出現的字符串中,我們選擇了逗號,作為分隔符號。
所以下一次分解應該倒下一個逗號處,
\(.*\)\.o[:]*,
這里首先看 .* 它表示匹配任意字符,\( \)是一個整體,也是通過上面的表格得到的,然后轉義字符\和.o在一起,把.的作用(匹配除換行符的單個字符)變成普通的.(就是一個字符.),那么這一句話就是
操作字符串所有有.o的且在.0后面(可以有空格)匹配:的零個或多個字符串。
objs/\1.o:,g
這里要解釋的是\1.o 這里用了轉義字符\加上1,這表示什么呢?尤其是這個1,表示的就是前面\( \)內的字符串,這是組
的概念,如何知道是第幾組呢?前面的第一個\(\)的就是第一組,用轉義字符\1表示,依次類推。g在sed中表示行內全局替換
這樣,我們做一個假設例子來說明。
abc.o : 用這個代表\(.*\)\.o[:]*
然后objs/\1.o:,g之后呢,abc.o :變成了 objs/abc.o: 這里相當于給前面的通用匹配加上了objs/前綴,并且把:和.o之前的空格去掉了
最后這個<$@.tmp >$@;這不屬于sed的內容了,屬于linux和Makefile的東西,$@.tmp重定向輸入給前面的sed替換操作,
$@代表目標在Makefile中,$@.tmp是前面的Makefile生成的,<重定向,看方向是輸入,
就是把$@.tmp重定向輸入給sed,經過sed替換之后,再輸出重定向 > 到$@,這個是目標。
這樣再回過頭去看之前那個Makefile就可以看懂了。
?