Makefile 8——使用依賴關系文件


Makefile中存在一個include指令,它的作用如同C語言中的#include預處理指令。在Makefile中,可以通過include指令將自動生成的依賴關系文件包含進來,從而使得依賴關系文件中的內容成為Makefile的一部分。

在此之前,先介紹一下Makefile中的include的用法。

 1 .PHONY:all clean
 2 DIR_DEP=dep
 3 DEPS=test_deps
 4 all: exe
 5 
 6 include $(DEPS)
 7 
 8 dep:
 9     mkdir dep
10 exe:
11     @echo "exe"
12 
13 test_deps:$(DIR_DEP)
14     @echo "deps"

 好好分析上圖的運行結果,能讓自己更好地理解后面的東西。
 
include”指示符告訴 make 暫停讀取當前的 Makefile,而轉去讀取“ include”
指定的一個或者多個文件,完成以后再繼續當前 Makefile 的讀取。 Makefile 中指示符
“ include”書寫在獨立的一行 。
通常指示符“include”用在以下場合:

1. 有多個不同的程序,由不同目錄下的幾個獨立的Makefile來描述其重建規則。它
們需要使用一組通用的變量定義或者模式
規則。通用的做法是將這些共同使用的變量或
者模式規則定義在一個文件中(沒有具體的文件命名限制),在需要使用的
Makefile中使用指示符“include”來包含此文件。
2. 當根據源文件自動產生依賴文件時;我們可以將自動產生的依賴關系保存在另
外一個文件中,主Makefile使用指示符“include”包含這些文件。這樣的做法
比直接在主Makefile中追加依賴文件的方法要明智的多。其它版本的make已經
使用這種方式來處理。
如 果 指 示 符 “ include ” 指 定 的 文 件 不 是 以 斜 線 開 始 ( 絕 對 路 徑 , 如
/usr/src/Makefile...),而且當前目錄下也不存在此文件; make將根據文件名試圖在以下
幾個目錄下查找:首先,查找使用命令行選項“-I”或者“--include-dir”

指定的目錄,如果找到指定的文件,則使用這個文件;否則繼續
依此搜索以下幾個目錄(如果其存在):“/usr/gnu/include”、“/usr/local/include”和
“/usr/include”。
當在這些目錄下都沒有找到“include”指定的文件時,make將會提示一個包含文
件未找到的告警提示,但是不會立刻退出。而是繼續處理Makefile的后續內容。當完成
讀取整個Makefile后,make將試圖使用規則來創建通過指示符“include”指定的但未
找到的文件(參考 3.7 makefile文件的重建 一節),當不能創建它時(沒有創建這個文
件的規則),make將提示致命錯誤並退出。

通常我們在 Makefile 中可使用“-include”來代替“include”,來忽略由於包含文
件不存在或者無法創建時的錯誤提示(“-”的意思是告訴 make,忽略此操作的錯誤。
make 繼續執行)。

我們改成-include之后:

這樣就沒有提示找不到那個目錄或文件了,但是我們必須確保有規則去創建include指定的內容,否則最后將出錯。

make的執行過程如下:
1. 依次讀取變量“MAKEFILES”定義的makefile文件列表
2. 讀取工作目錄下的makefile文件(根據命名的查找順序“GNUmakefile”,“makefile”,“Makefile”,首先找到那個就讀取那個)
3. 依次讀取工作目錄makefile文件中使用指示符“include”包含的文件
4. 查找重建所有已讀取的makefile文件的規則(如果存在一個目標是當前讀取的某一個makefile文件,則執行此規則重建此makefile文件,完成以后從第一步開始重新執行)
5. 初始化變量值並展開那些需要立即展開的變量和函數並根據預設條件確定執行分支
6. 根據“終極目標”以及其他目標的依賴關系建立依賴關系鏈表
7. 執行除“終極目標”以外的所有的目標的規則(規則中如果依賴文件中任一個文件的時間戳比目標文件新,則使用規則所定義的命令重建目標文件)
8. 執行“終極目標”所在的規則

知道了include優先於本Makefile的目標運行之后,來看我們的complicated項目:

 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 
15 EXE=complicated
16 EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
17 SRCS=$(wildcard *.c)
18 OBJS=$(SRCS:.c=.o)
19 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
20 DEPS=$(SRCS:.c=.dep)
21 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))
22 
23 all: $(EXE)
24 
25 include $(DEPS)
26 
27 $(DIRS):
28     $(MKDIR) $@
29 $(EXE):$(DIR_EXES) $(OBJS)
30     $(CC) -o $@ $(filter %.o,$^)
31 $(DIR_OBJS)/%.o:$(DIR_OBJS) %.c 
32     $(CC) -o $@ -c $(filter %.c,$^)
33 $(DIR_DEPS)/%.dep:$(DIR_DEPS) %.c
34     @echo "Creating $@ ..."
35     @set -e;\
36     $(RM) $(RMFLAGS) $@.tmp;\
37     $(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
38     sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\
39     $(RM) $(RMFLAGS) $@.tmp
40 clean:
41     $(RM) $(RMFLAGS) $(DIRS) 

這里增加了filter函數,具體可以看前面函數那一篇隨筆。正如前面所提及的,當make看到include指令時會試圖去構建所需包含進來的依賴文件,這樣就不必在顯式地讓all目標依賴它了。這也是我舉第一個例子的原因,有了include,make會自動去 構建 依賴。所以,在complicated項目中,我們在每一個依賴項之前都添加了一個先決條件,這個先決條件就是每一個依賴的目錄。

 

需要指出地是,上面的代碼可能會無限循環。

如果你的編譯器安裝在FAT32文件系統上,將可以運行不會無限循環,但是如果是在NTFS文件系統上,會死循環。筆者的Linux上是無限循環了。

出現無限循環的原因和文件系統有關,有的文件系統當目錄中的文件被更改時,目錄時間戳隨之更改,由於在Makefile中創建依賴關系時,制定了deps目錄是其第一個先決條件,於是,deps目錄時間戳地改變使得make又一次使用規則再次創建main.dep 和foo.dep,這樣造成了無限循環。

既然發現了問題,證明我們這個Makefile存在bug,需要更改,基本思路是:

如果deps目錄不存在,則讓deps目錄成為規則的第一個先決條件;

如果deps目錄已經存在,則不讓deps目錄出現在規則的先決條件中。

沿着這個思想走下去,需要用到Makefile中的條件語法。

 關鍵字“ ifeq
此關鍵字用來判斷參數是否相等,格式如下:
`ifeq (ARG1, ARG2)
`ifeq 'ARG1' 'ARG2''
`ifeq "ARG1" "ARG2"'
`ifeq "ARG1" 'ARG2''
`ifeq 'ARG1' "ARG2"'
替換展開“ ARG1”和“ ARG1”后,對它們的值進行比較。如果相同則(條件為
真)將“ TEXT-IF-TRUE”作為 make 要執行的一部分,否則將“ TEXT-IF-FALSE”作
make 要執行的一部分(上邊的第二種格式)。

還有ifdef,ifndef和ifeq,ifneq用法類似。

關鍵字“ ifdef
關鍵字“ ifdef”用來判斷一個變量是否已經定義。格式為:
`ifdef VARIABLE-NAME'
如果變量“ VAEIABLE_NAME”的值非空(在 Makefile 中沒有定義的變量的值為空),
那么表達式為真,將“ TEXT-IF-TRUE”作為 make 要執行的一部分。否則,表達式為
假,如果存在“ TEXT-IF-FALSE”,就將它作為 make 要執行一部分。當一個變量沒有
被定義時,它的值為空。“ VARIABLE-NAME”可以是變量或者函數的引用。
對於“ ifdef”需要說明的是: ifdef 只是測試一個變量是否有值,不會對變量進行
替 換 展 開 來 判 斷 變 量 的 值 是 否 為 空 。 對 於 變 量 “ VARIABLE-NAME ” , 除 了
VARIABLE-NAME=”這種情況以外,使用其它方式對它的定義都會使“ ifdef”返回
真。就是說,即使我們通過其它方式(比如,定義它的值引用了其它的變量)給它賦了
一個空值,“ ifdef”也會返回真。我們來看一個例子:
1
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
2
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
1 中的結果是:“ frobozz = yes”;而例 2 的結果是:“ frobozz = no”。其原因就
是在例 1 中,變量“ foo”的定義是“ foo = $(bar)”。雖然變量“ bar”的值為空,但是
ifdef”判斷的結果是真。因此當我們需要判斷一個變量的值是否為空的情況時,需要
使用“ ifeq”(或者“ ifneq”)而不是“ ifdef”。

 運用條件語法后的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 
15 EXE=complicated
16 EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
17 SRCS=$(wildcard *.c)
18 OBJS=$(SRCS:.c=.o)
19 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
20 DEPS=$(SRCS:.c=.dep)
21 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))
22 
23 ifeq ("$(wildcard $(DIR_OBJS))","")
24 DEP_DIR_OBJS :=$(DIR_OBJS)
25 endif#dir_objs
26 ifeq ("$(wildcard $(DIR_EXES))","")
27 DEP_DIR_EXES :=$(DIR_EXES)
28 endif#dir_exes
29 ifeq ("$(wildcard $(DIR_DEPS))","")
30 DEP_DIR_DEPS :=$(DIR_DEPS)
31 endif#dir_deps
32 
33 all: $(EXE)
34 
35 include $(DEPS)
36 
37 $(DIRS):
38     $(MKDIR) $@
39 $(EXE):$(DEP_DIR_EXES) $(OBJS)
40     $(CC) -o $@ $(filter %.o,$^)
41 $(DIR_OBJS)/%.o:$(DEP_DIR_OBJS) %.c 
42     $(CC) -o $@ -c $(filter %.c,$^)
43 $(DIR_DEPS)/%.dep:$(DEP_DIR_DEPS) %.c
44     @echo "Creating $@ ..."
45     @set -e;\
46     $(RM) $(RMFLAGS) $@.tmp;\
47     $(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
48     sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\
49     $(RM) $(RMFLAGS) $@.tmp
50 clean:
51     $(RM) $(RMFLAGS) $(DIRS) 

 

 

這樣就不會無限循環了,同樣,如果不想make報那個警告沒有什么那個文件或目錄,在Makefile中的include加上符號 ’-‘,這個提示信息在這里是安全的,因為make就是這樣設計的include指令。 改動主要是增加了三個變量,這三個變量的值根據相應的目錄是否存在而分別賦值。如果不存在,就將目錄名賦值給它,如果存在,則這三個變量的值為空,在Makefile中,就算沒有定義一個變量,直接$(變量),此時變量為空,增加的三個變量都作為對應規則中的第一個先決條件,這樣無限循環問題得到了解決。

但是,我很納悶,為什么加了條件語法,這個死循環的問題就解決了?就算加了條件語法,生成在dep目錄下的文件依舊會在我的文件系統上改變時間戳,那樣還是會一直循環啊,為什么這里卻得到了解決?這就要到最前面的Makefile中說起了,時間戳改變,make會去重新構建時間戳改變了的所以依賴內容,有了條件語句之后,就算時間戳改變了,但是條件語句會生成對應地條件來阻止無限循環,雖然條件語句沒有放在類似C語言的while(1)這種無限循環的語句塊中,但是時間戳的改變,會讓make重新構建和時間戳改變文件的所以依賴,這樣條件語句相當於是一直在while(1)中一樣。最后說明的是,條件語句很像C語言中的條件編譯,它應該在構建目標之前預編譯,所以通常條件語法應該位於目標之前,如果放在目標執行完畢之后,條件語句將失去作用,畢竟條件語句是要去控制目標和依賴項的,位於它們之前也是理所當然的,Makefile中語句的大體運行順序在上面有給出。

 

1 ifeq ($(wildcard $(DIR_OBJS)),)
2 DEP_DIR_OBJS :=$(DIR_OBJS)
3 endif#dir_objs
4 ifeq ($(wildcard $(DIR_EXES)),)
5 DEP_DIR_EXES :=$(DIR_EXES)
6 endif#dir_exes
7 ifeq ($(wildcard $(DIR_DEPS)),)
8 DEP_DIR_DEPS :=$(DIR_DEPS)
9 endif#dir_deps

 

也可以用上面的代替之前Makefile中的三個變量部分,之前用的空“”,括號中還加了引號“,其實按照GNU_MAKE上的示例,用的ifeq(XX,)表示如果XX為空就...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM