Makefile 自動生成頭文件的依賴關系 .


最近在看一本書《Windows游戲編程大師技巧》 (Tricks of Windows Game Programming Gurus). 第一章給出了一個打磚塊小游戲的示例程序. 包括三個文件: blackbox.h, blackbox.cpp和freakout.cpp (600行代碼, 對於Windows C++程序來說還好, 沒有讓我freak out…). blackbox.cpp封裝了部分DirectDraw, 提供了一些更傻瓜化的初始化DirectDraw, 畫點, 畫方框的工具函數. blackbox.h包括了這些函數的聲明. freakout.cpp引用了blackbox.h文件, 包括WinMain主函數和主要的游戲邏輯.

How to build?

和我一樣還在使用N年前的垃圾電腦的看官們, 多半也不願意用Visual Studio來挑戰自己的耐心. 但是所以讓我們選擇小米加步槍: GCC (MinGW) + Makefile. 寫個簡單的makefile例如:

all:    freakout.exe
 
freakout.exe: freakout.o blackbox.o
    g++ freakout.o blackbox.o -o stupid.exe -L D:gamedevdx81sdkDXFDXSDKlib -lddraw -mwindows
 
freakout.o: freakout.cpp blackbox.h
    g++ -c freakout.cpp -o freakout.o -I D:gamedevdx81sdkDXFDXSDKinclude
 
blackbox.o: blackbox.cpp blackbox.h
    g++ -c blackbox.cpp -o blackbox.o -I D:gamedevdx81sdkDXFDXSDKinclude

Problem?

上面的代碼當然有很多問題, 比如”-L <DX lib path>”, “-I <DX inc path>”可以被定義在類似$(LIB), $(INC)的變量里面, 比如我沒有按照makefile的習慣寫一個clean目標清理生成的東東…不過我的重點是在紅色高亮的blackbox.h: 兩個.o文件都需要依賴於blackbox.h.

假設我們修改freakout.cpp引用更多的頭文件, 每加一條#include “somefile.h”指令, 就需要相應地在makefile里為freakout.o增加一個依賴項somefile.h.

假設我們修改stupid.h文件, 讓stupid.h也引用一個頭文件someotherfile.h, 那么我們也需要相應地在makefile里為所有依賴stupid.h的.o文件 (在這個例子中是freakout.o和blackbox.o) 增加依賴項someotherfile.h.

所以問題是: 在.cpp和.h被修改的情況下, 怎么維護.o目標文件和.h頭文件的依賴關系.

Solution – 自動生成依賴關系

Google一下”makefile 頭文件 依賴”會發現大多數編譯器都提供了一個選項生成.o目標文件所依賴的文件列表. 比如GCC的”-MM”選項. 運行GCC –MM freakout.cpp blackbox.cpp <庫文件和頭文件選項>得到輸出:

freakout.o: freakout.cpp blackbox.h D:/gamedev/dx81sdk/DXF/DXSDK/include/ddraw.h
blackbox.o: blackbox.cpp blackbox.h D:/gamedev/dx81sdk/DXF/DXSDK/include/ddraw.h

所以一個簡單的處理依賴關系的辦法是把這些編譯器生成的依賴關系寫入一個文件里, 然后在makefile中用include指令包含這個文件:

CPP = g++
OBJ = freakout.o blackbox.o
LIB = -L D:gamedevdx81sdkDXFDXSDKlib -mwindows -l ddraw
INC = -I D:gamedevdx81sdkDXFDXSDKinclude
 
all: freakout.exe
 
freakout.exe: ${OBJ}
    ${CPP} ${OBJ} -o freakout.exe ${LIB}
 
include depend
 
# note the symbols: $< and '$@
%.o: %.cpp
    ${CPP} -c $< -o $@ ${INC}
 
# generate depend file
depend:
    ${CPP} -MM ${OBJ:.o=.cpp} ${INC} > depend

注意紅色的部分, depend是GCC生成的包含了依賴關系的文件 (也注意上面的makefile中有”$<”, “$@”這種perl風格的bt匹配字符…) . 有了這樣的makefile, 我們就能用兩個步驟來build項目:

1) 在需要更新依賴關系的時候 (比如在某個文件里多加了一條#include指令) 運行make depend.

2) 運行make.

Makefile Auto Dependency – 只運行一次make

懶惰是程序員的一大美德, 所以GNU make的手冊里提供了一個運行一次make就能更新所有依賴關系並且按照依賴關系build的辦法 (http://www.gnu.org/software/make/manual/make.html#Automatic-Prerequisites): 與整體引入一個depend目標不同, 我們為每一個.o/.cpp文件引入一個.d依賴關系文件. 依賴關系文件由GCC的”-MM”選項生成, 並且在GCC輸出的基礎上把自己本身加入到目標列表中. 比如:

# freakout.d
freakout.o freakout.d: freakout.cpp D:/gamedev/dx81sdk/DXF/DXSDK/include/ddraw.h blackbox.h
 
# blackbox.d
blackbox.o blackbox.d: blackbox.cpp D:/gamedev/dx81sdk/DXF/DXSDK/include/ddraw.h blackbox.h

注意紅色的部份: .d文件被加入到了目標列表, 依賴相關的.h和.cpp文件. 那么怎樣自動生成這些.d文件呢? 類似的, makefile:

 

CPP = g++
OBJ = freakout.o blackbox.o
LIB = -L D:gamedevdx81sdkDXFDXSDKlib -mwindows -l ddraw
INC = -I D:gamedevdx81sdkDXFDXSDKinclude
all: freakout.exe
freakout.exe: ${OBJ}
    ${CPP} ${OBJ} -o freakout.exe ${LIB}
include ${OBJ:.o=.d} 
%.o: %.cpp
    ${CPP} -c $< -o $@ ${INC} 
%.d: %.cpp
    rm -f $@ & 
    ${CPP} -MM $< ${INC} > $@.$$ & 
    insertdfile.exe $< $@.$$ > $@ & 
    rm -f $@.$$

這個makefile和上一節給出的makefile大致差不多, 不同的是兩個紅色的部分: 第一部分我們用include指令include相關的所有.d文件; 第二部分定義了生成.d文件的規則 (又是$@, $<符號…): 第一行首先刪除原有的.d文件; 第二行運行g++ -MM生成依賴關系寫入一個臨時文件($@.$$)里; 第三行把.d文件加入到第二行生成的依賴關系中並把最終結果寫到.d文件中; 第四行刪除臨時文件.

P.S. insertdfile.exe是我自己寫的一個小程序, 用來把.d文件加入到第二行生成的依賴關系中, 例如把”blackbox.o: blackbox.cpp blackbox.h”轉換為”blackbox.o blackbox.d: blackbox.cpp blackbox.h”. 我為什么要自己寫一個字符串替換的程序? 因為我沒有裝sed工具…如果有, 當然用官方推薦的辦法, 把第三行換成神奇的符咒: sed ’s,($*).o[ :]*,1.o $@ : ,g’ < $@.$$ > $@ .

好了, 運行make:

1) make嘗試去包含blackbox.d和freakout.d文件, 沒有發現, 所以檢查有沒有規則可以生成這些.d文件, 發現我們%.d: %.cpp的這條規則, 於是運行這條規則生成所有的.d文件並且包含進來.
2) 按照正常規則生成.o文件, 然后生成.exe.

然后我們做一些修改, 比如增加一個stupid.h文件, 然后修改blackbox.h文件以包含stupid.h. 運行make:

1) make把第一個目標all加入到需要生成的目標列表中.
2) make包含blackbox.d和freakout.d文件. 注意在這個過程中這兩個.d文件也會被加入到make的需要生成的目標列表中 – 因為可能有規則能更新這兩個.d文件.
3) 根據blackbox.d和freakout.d包含的規則: .d文件 (已經被入到了make的需要生成目標列表中) 需要被更新, 於是相應地運行%.d: %.cpp規則更新.d文件: 新加入的 stupid.h文件被加入到兩個.d文件的依賴項中.
4) make發現包含的兩個.d文件在第2)步中都被更新, 於是重新包含這兩個.d文件.
5) make根據在第2)步生成在第3)步被包含進來的規則, 發現stupid.h的日期比兩個.o文件的日期都要新, 於是重新生成.o文件.
6) 根據規則生成.exe.


免責聲明!

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



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