說到軟體專案的依賴管理,可以從三個方面來考慮:
一、由build system控制的dependency
現在的build system,都支援一定程度上的dependency management, 比如make支援target之間的dependency,ant也支援其每個target之間的dependency(區別是make的每個非PHONY的target是個檔案,make會檢查輸入與輸出之間的timestamp來達到incremental build的效果,而ant則是對上一次build沒有任何記憶,除了javac task支援incremental compile)
上面的dependency講的主要是如何確定build的順序,但dependency管理的另一個非常重要的目標,是自動獲取dependency,並設定好:
- 對於C++來講:includedir(-I), libdir(-L)
- 對於Java來講:classpath
手工的維護這些path,非常麻煩而且容易出錯,因為我們在一個過低的level管理這些資訊 - 事實上,正確的做法應該是:使用者只需宣告我依賴這個component或者我依賴這個library,dependency manager自動幫我設定好這些path。
總的來講,dependency manager有這麼幾個作用:
- 讓使用者在合適的邏輯層次宣告dependency,自動設定好需要的path
- Resolve transitive dependency
- Conflict manage (diamond dependency)
這些功能都不是手工能夠解決的。
二、不由build system控制的dependency
如果有兩個或者多個不同的project,由不同的team開發,甚至使用了不同的build system,那麼基本上是不可能把他們放在同一個build pipeline中編譯的。但是他們之間又有dependency,該怎麼處理?
這裡其實就是一個升級的問題, 比如:
B -> A/1.0
而A還是不斷的開發,可能會改變已有的實現(引起B的執行時錯誤),也可能改變已有的介面(引起B的編譯時錯誤),如果B在一段時間之後升級到A的新版本A/1.1,B可能需要很長時間做migration - 或者說integration。其實從continuous integration的角度來理解這個問題,正確的做法應該是:
A要儘可能快的推出一個新的release,而B要立馬跟上,這樣每次B的改動都非常小,不容易出錯,出了錯也容易解決。
但推出一個新的release畢竟不是件小事,很多team會有自己的擔心:資源不夠,risk太高等等 - 所以上述是一個比較理想的情況。在達到那個情況之前,可以另外有個方案:
B繼續使用A/1.0, 但是同時B新開出一個branch:B/edge,依賴於最新的A的程式碼,A的每次build,都會trigger B/edge的build (edge build),這樣保證B總是build against最新的A,任何問題都可以在第一時間發現,並fix
關於edge build,值得另外寫一篇文章討論一下。
這裡,這種不由build system控制的dependency,就需要由其他系統來控制,比如各種CI server中的job,一個job會trigger另外一個job,也是一種dependency關係,剛好對應起來。
三、dependency graph的顯示
軟體專案之間的dependency graph,很好的反映了各個專案之間的關係,注意這個dependency的單位不是project,而是release,這更動態的反映出了依賴關係,同時,由於你知道每個專案的dependency,根據這個資訊就能建出一張完整的dependency graph,從而能得出所有consumer的資訊,於是,我就有了關於這個專案的所有dependency和consumer的資訊:
- 我用了那些專案,用的版本會不會太老?
- 我被哪些專案用了?受不受歡迎?是不是可以停止支援某個release了(如果沒有太多consumer的話)
顯示一個專案的dependency時,可以有以下幾部分:
- 直接dependency
- 間接dependency
- 直接consumer
- 間接consumer
- dependency graph
關於dependency graph,要注意的是:首先它是一張完整的圖,正確的顯示了所有直接dependency和間接dependency,但是這樣的圖,對於一個大的專案來講的話,會比較亂 - 考慮某個專案即是直接dependency,又是間接dependency的情況,就會有多個箭頭指向它。所以一般顯示的時候,會將其的transitive reduction顯示出來(tred,dot自動做reduction),在保證其reachbility的同時,減少邊的數量,使圖看起來比較簡練:
還有要注意的一點是,我們需要提供額外的metadata,來保證這個圖的正確性。以ivy為列,ivy.xml提供了比較完整的dependency資訊(沒有類似機制的build system至少要產生這種metadata),但其問題是對於有dependency conflict的情況(diamond dependency),如上面第二張圖,如果b和c依賴於不同版本的d,單憑ivy.xml無法確認到底選d的那個版本,這是ant/ivy在build過程當時選擇一定的conflict manager方式確定下來的,而這個資訊,必須以某種方式告訴dependency graph - 一般的方式就是在build過程中產生metadata並予以儲存。
對於沒有ivy.xml這種機制的build system,需要產生metadata一儲存:
- 所有直接的dependency
- 有conflict的間接dependency
然後可以根據所有專案的這個資訊產生正確的dependency graph