1. 程式人生 > >Makefile 8——使用依賴關系文件

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

cat ddp 地理 但是 代碼 終極 源文件 無限循環 prefix

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 %.o,$^)
33 $(DIR_DEPS)/%.dep:$(DIR_DEPS) %.c
34     @echo "Creating $@ ..."
35     @set -e;36     $(RM) $(RMFLAGS) [email protected];37     $(CC) -E -MM $(filter %.c,$^) > [email protected];38     sed s,\(.*\)\.o[:]*,objs/\1.o:,g <[email protected] >$@;39     $(RM) $(RMFLAGS) [email protected]
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中的條件語法。

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