2008年2月11日 星期一

開發工具 (2): cc wrapper

前一篇的 ld wrapper 解決的是函式庫連結順序的問題,而這一篇的 cc wrapper 解決的是標頭檔相依性的問題。

在某些專案內,常常會碰到需要繼承一個通用模組,然後實作底下的方法來執行一些特定的動作,而最常見的開發方式便是複製一個已經會動的模組,把裡面的主邏輯換掉,直接套用已經寫好的介面或者輔助函式。 這的確是最簡單的方式,但是也造成了很多後遺症。

舉例來說,一個檔案被複製好幾次以後,每個人都加上了自己所需的標頭檔。有一天老闆說,去寫一個 foo 的模組吧,拿 bar 來改就行了。開發者把 bar 的檔案打開一看,洋洋灑灑前面放了幾十個標頭檔,想必 bar 本身也是複製過好幾手的模組了。這些標頭檔,也許留著並不會影響現在程式的編譯,但是卻會破壞程式的相依性並且拖長編譯時間,甚至日後被其中之一的標頭檔 的變動而拖累變成無法編譯。

這時候,開發者怎麼決定這幾十個標頭檔哪一些有用到而該留著,哪一些沒用到而要砍掉?

cc wrapper 就是來解決這個問題。它的用法和 ld wrapper 一樣,都是藉著類似 ccache 的方式來執行,把執行檔放在系統之中的任何位置,然後在環境路徑裡面優於 /usr/bin 的地方放一些名叫做 cc c++ gcc g++ 的連結指向這支 cc wrapper,或者直接定義環境變數 CC 和 CXX 指向這些連結,接下來的工作便交給 cc wrapper 執行。

cc wrapper 會掃瞄 *.c *.cc *.cpp 之類的原始碼檔案,找出標頭檔,並且利用「真 cc」的 -M 功能,決定這些標頭檔的真實路徑,加上呼叫「假 cc」時傳入的 -D 變數,cc wrapper 無須擔憂條件式編譯(-DFREEBSD 與 #ifdef FREEBSD ... #include ... #endif)以及多重引用路徑(程式中有 #include ,但是 blah.h 同時出現在 /usr/include 和 /usr/local/include)造成的問題。

接著 cc wrapper 會檢查標頭檔之間的相依關係,並且確認該原始檔是不是真的需要該標頭檔。在某些情況下,多餘的標頭檔並無法被掃乾淨,例如 a.c 拉了 b.h c.h d.h,而 b.h 拉了 d.h,所以即使 a.c 裡面只用了 b.h 和 c.h,而完全不使用 d.h 所定義的函式或變數,cc wrapper 也只能列出來說「a.c 可能不需要 d.h」而不能直接斷定「a.c 不需要 d.h」,因為即使從 a.c 中移除 d.h,該 d.h 一樣會跟著 b.h 被帶入編譯。

另外 cc wrapper 會幫助程式開發者把標頭檔放在正確的位置。例如 a.c 裡面拉了 a.h,而 a.h 拉了 b.h,但是 a.c 使用了 b.h 所定義的函式,反而 a.h 的宣告和 b.h 無關,這時候 cc wrapper 會先警告「a.h 不需要 b.h」,而當開發者從 a.h 移除了 b.h 後,將會造成 a.c 的編譯失敗,這時候開發者再把 #include "b.h" 補回 a.c,這才是正確的位置。

有了 cc wrapper,將會有效的抓出程式中用不到的標頭檔,降低維護的成本,減少編譯出錯的機率。

0 意見: