依附元件管理

回報問題 查看原始碼 Nightly · 7.4 . 7.3 7.2 7.1 7.0 6.5

在查看先前幾頁時,您會發現一個主題不斷重複:管理自己的程式碼相當簡單,但管理其依附元件則困難許多。依附元件有很多種:有時候 對特定工作的依賴 (例如「在將版本標示為版本前推送說明文件」 有時也會有依附關係 (例如「我需要 才能使用最新版的電腦視覺資料庫來建構程式碼」)。 有時,您有程式碼集的另一部分有內部依附元件,且 有時候,會對其他團隊擁有的程式碼或資料,造成外部依賴 (貴機構或第三方皆可)。但無論如何 我必須先瞭解事情才能做這個」」的任務,我會重複發生在 以及管理依附元件 是建構系統的基本工作

處理模組和依附元件

使用 Bazel 等構件型建構系統的專案會拆分成一組 模組,以及透過 BUILD 表示依附元件的模組 檔案。妥善整理這些模組和依附元件 對建構系統效能以及需要的工作量 維護。

使用精細的模組和 1:1:1 規則

建構以構件為基礎的版本時,第一個想出的問題是 決定個別模組應包含多少功能。在 Bazel 中 模組是以指定可建構單元為目標, java_librarygo_binary。例如,整項專案 將一個 BUILD 檔案放在根目錄,然後 以遞迴方式記錄專案的所有來源檔案。另一端 不過,幾乎每個來源檔案都能加入各自的模組,有效 要求每個檔案都列在 BUILD 檔案中,並依附於其他所有依附的檔案。

大多數專案介於這些極端之間 在效能和可維護性之間取得平衡為整個專案使用單一模組,可能表示您除了新增外部依附元件時,不需要再碰觸 BUILD 檔案,但這也表示建構系統必須一律一次建構整個專案。也就是說 平行處理或分配建構作業的各個部分 預先建構完成每個檔案一個模組則相反:建構系統 對建構在快取和排程步驟方面擁有最大的彈性,但 因此工程師必須投入更多心力維護依附元件清單 就會變更檔案參照

雖然精確的精細程度因語言而異 (甚至在同一種語言內也可能不同),但 Google 傾向於採用比以任務為基礎的建構系統中通常編寫的模組更小得多的模組。Google 的一般正式版二進位檔通常會依賴數萬個目標,即使是規模中等的團隊,其程式碼庫中也可能有數百個目標。對於 Java 等具有強大內建封裝概念的語言,每個目錄通常都包含單一套件、目標和 BUILD 檔案 (Pants 是另一個以 Bazel 為基礎的建構系統,稱此為 1:1:1 規則)。套用包裝慣例較少的語言,通常會在每個 BUILD 檔案中定義多個目標。

較小的建構目標帶來的好處會在規模上開始顯現,因為這類目標可加快分散式建構作業,且不需經常重建目標。測試輸入圖片後,優點更是更吸引人。 目標越精細,建構系統就能變得更聰明 只執行一部分可能受特定指定影響的測試 變更。Google 相信使用較小目標可帶來系統性效益,因此我們已投資開發工具來自動管理 BUILD 檔案,以免造成開發人員負擔,並在一定程度上減輕了使用較小目標的缺點。

其中部分工具 (例如 buildifierbuildozer) 可在 buildtools 目錄中使用 Bazel。

最小化模組顯示設定

Bazel 和其他建構系統允許每個目標指定瀏覽權限。 屬性來決定其他目標依附於該屬性。私人目標 只能在自己的 BUILD 檔案中參照。目標可以將更廣泛的瀏覽權限授予明確定義的 BUILD 檔案清單目標,或是在公開瀏覽權限的情況下,將瀏覽權限授予工作區中的每個目標。

如同大多數程式設計語言,盡可能減少可見度通常是最佳做法。一般來說,Google 團隊只會將目標設為公開,如果這些目標代表 Google 任何團隊都可使用的廣泛使用的程式庫。團隊要求其他團隊協調,才能使用他們的程式碼, 維護客戶目標許可清單,以便監控客戶的成效。每個團隊的內部實作目標都會受到限制,只能是團隊擁有的目錄,而且大多數 BUILD 檔案都只有一個非私密的目標。

管理依附元件

模組必須能夠彼此參照。將程式碼集區分割成精細的模組的缺點是,您需要管理這些模組之間的依附元件 (雖然工具可協助自動化這項作業)。表示這些依附元件通常會成為 BUILD 檔案中的主要內容。

內部依附元件

在大型專案中細分為精細模組,大部分的依附元件 可能是內部資料;也就是部署於另一個目標上 原始碼存放區內部依附元件與外部依附元件的 而不是以預先建構的構件下載 一併執行建構作業這也表示內部依附元件沒有「版本」的概念,也就是說,目標和所有內部依附元件一律會在存放區的相同提交/修訂版本中建構。在處理內部依附元件時,必須謹慎處理的一個問題,就是如何處理傳遞依附元件 (圖 1)。假設目標 A 依附於目標 B, 這取決於通用的程式庫目標 C。目標 A 應該可以使用類別 目標 C 所定義?

遞移依附元件

圖 1. 遞移依附元件

就基礎工具而言,這並沒有問題。在建構時,B 和 C 都會連結至目標 A,因此 A 會知道 C 中定義的任何符號。Bazel 已提供這樣的技術多年,但隨著 Google 的發展, 開始出現問題假設 B 已重構,因此不再需要依附 C。如果 B 對 C 的依附元件遭到移除,則 A 和任何其他 透過 B 上的依附元件使用 C 的目標則會中斷。實際上,目標 依附元件成為公開合約的一部分 已變更。也就是說,Google 會持續累積依附元件

Google 最終在 Bazel 中導入「嚴格傳遞依附元件模式」,解決了這個問題。在這個模式中,Bazel 會偵測目標是否會嘗試參照符號,但未直接依附該符號,如果是的話,就會失敗並顯示錯誤,以及可用來自動插入依附元件的殼層指令。將這項變更導入 Google 的整個程式碼集 重構每個數百萬個建構目標,明確列出 依附元件通常需要多年時間,但絕對值得。我們的版本 速度更快,因為目標的非必要依附元件較少 工程師有權移除不必要的依附元件 破壞依賴這些目標。

如往常,強制執行嚴格的遞移依附元件會涉及權衡。這會使建構檔案變得冗長,因為現在需要在許多位置明確列出常用的程式庫,而不是隨機擷取,而工程師需要花費更多心力在 BUILD 檔案中新增依附元件。我們開發了可自動偵測許多缺少的依附元件,並在不需要開發人員介入的情況下將其加入 BUILD 檔案的工具,藉此減少這項工作所需的勞力。不過,即使沒有這類工具,我們發現在程式碼庫擴充時,權衡利弊還是值得的:明確新增依附元件至 BUILD 檔案是一次性成本,但處理隱含的傳遞依附元件可能會導致持續性問題,只要建構目標存在,就會發生這種問題。Bazel 強制執行嚴格的遞移依附元件 安裝在 Java 程式碼中

外部依附元件

如果依附元件不是內部,則該依附元件必須是外部項目。外部依附元件 在建構系統外建構和儲存的構件上,產生相應的預測結果。依附元件會直接從構件存放區 (通常透過網際網路存取) 匯入,並且會原封不動地使用,而非從原始碼建構。外部依附元件和內部依附元件之間最大的差異之一,在於外部依附元件有版本,而這些版本與專案的來源程式碼無關。

自動與手動依附元件管理

建構系統可允許管理外部依附元件的版本 手動或自動手動管理時,建構檔案會明確列出要從構件存放區下載的版本,通常會使用 1.1.4語意版本字串。如果自動管理,來源檔案會指定 且建構系統會一律下載最新版本。適用對象 例如,Gradle 允許將依附元件版本宣告為「1.+」 可接受任何次要或修補版本的依附元件,只要 主要版本為 1

自動代管的依附元件對於小型專案是便捷的,但 往往是一種災難因應做法, 目前是由多位工程師合作自動指出的問題 這表示您無法控管 YAML 檔案 已更新。目前沒有任何方法能確保外部方不會造成破壞 更新 (即使他們聲稱使用語意版本管理),因此建構 隔天工作可能會分崩離析 或復原為工作狀態即使建構作業沒有中斷,也可能發生難以追蹤的細微行為或效能變更。

相對的,因為手動管理的依附元件需要變更來源 所以您可以輕鬆尋找、復原 查看舊版存放區,使用較舊的依附元件進行建構。 Bazel 規定所有依附元件的版本都必須手動指定。即使規模適中,手動版本管理的額外負擔也值得,因為這能帶來穩定性。

單一版本規則

不同版本的程式庫通常會以不同的構件表示,因此理論上,同一個外部依附元件的不同版本,不應在建構系統中以不同的名稱宣告。如此一來,每個目標就能選擇想要的依附元件版本 相關單位會如何運用資料,並讓他們覺得自己 獲得充分告知,且能夠針對該使用方式表示同意這會造成許多實務問題,因此 Google 會嚴格執行相關政策 單一版本規則 用於程式碼集中的所有第三方依附元件

要允許多個版本的最大問題在於鑽石依賴性 問題。假設目標 A 依附於目標 B 和外部第 1 版 資源庫。如果目標 B 稍後重構,以便在相同外部程式庫的 v2 上新增依附元件,目標 A 就會中斷,因為它現在會隱含地依附相同程式庫的兩個不同版本。而且,新增 將新的依附元件從目標轉移到擁有多個版本的第三方程式庫 因為該目標的使用者 可能取決於 版本。遵循「單一版本規則」可避免發生這種衝突,如果目標在第三方程式庫中新增依附元件,任何現有的依附元件都會位於該版本,因此可以順利共存。

遞移外部依附元件

處理外部依附元件的遞移依附元件可能包括 就特別困難Maven Central 等許多構件存放區 藉此指定雲端中其他構件特定版本的依附元件 存放區Maven 或 Gradle 等建構工具通常會以遞迴方式下載 預設遞移依附元件,意味著在 可能會導致大量下載構件 。

這非常方便:在新增程式庫依附元件時,如果要追蹤該程式庫的每個傳遞依附元件,並手動新增所有依附元件,將會非常麻煩。但這也有很大的缺點:由於不同程式庫可能會依附相同第三方程式庫的不同版本,因此這種做法必然違反「一個版本」規則,並導致菱形依附元件問題。如果您的目標仰賴使用 同一依附元件的不同版本,系統不會告訴您 get。這也表示,如果新版本開始擷取部分依附元件的衝突版本,更新外部依附元件可能會導致整個程式碼集出現看似不相關的失敗情形。

因此,Bazel 不會自動下載遞移依附元件。 可惜的是,這沒有萬事通 — Bazel 的替代方案是 列出存放區外部每一個外部的單一檔案 整個依附元件中用於該依附元件的明確版本 Cloud Storage 也提供目錄同步處理功能幸好,Bazel 提供的工具能夠自動 產生這類檔案,當中包含一組 Maven 的遞移依附元件 導致學習失真性這項工具可讓您執行一次,以產生初始 WORKSPACE 檔案 您就能手動更新該檔案來調整版本 每個依附元件的結構中

這裡的選擇又是便利性和可擴充性之間的抉擇。小型專案可能不想自行管理傳遞式依附元件,因此可以使用自動傳遞式依附元件。隨著機構規模變革,這項策略越來越缺乏吸引力 隨著程式碼集不斷擴增 衝突和非預期的結果也變得越來越豐富 採用更大規模的架構時,手動管理依附元件的成本大不相同 低於自動依附元件導致的問題處理成本 以自動化做法管理成本

使用外部依附元件快取建構結果

外部依附元件通常是由第三方提供 穩定版的程式庫,可能不需要提供原始碼。只有部分通知 機構也可能會選擇使用自己的程式碼 可讓其他程式碼片段依附於第三方 比內部依附元件多出如果構件建構速度較慢,但下載速度較快,這項做法理論上可加快建構速度。

然而,這也會產生許多負擔和複雜性:必須有人 建構這些構件並上傳至 構件存放區,以及用戶端必須 最新版本。由於系統的不同部分會從存放區的不同位置建構,且來源樹狀結構不再一致,因此偵錯作業也變得更加困難。

如要解決產物建構時間過長的問題,建議您使用支援遠端快取的建構系統,如前文所述。這樣的建構系統會將每個建構作業的產出成果保存到工程師共用的地點,因此如果開發人員依賴其他人最近建構的構件,建構系統會自動下載該構件,而非建構該構件。這可提供直接依附成果物所帶來的所有效能優勢,同時確保建構作業與從相同來源建構的作業一樣一致。這是 而 Bazel 也可設為使用遠端指令 快取。

外部依附元件的安全性和可靠性

根據第三方來源的構件,可能會有風險。如果第三方來源 (例如構件存放區) 發生異常,就會造成可用性風險,因為如果整個建構作業無法下載外部依附元件,可能會停滯不前。這也存在安全性風險:如果第三方系統遭到攻擊者入侵,攻擊者可能會將參照的構件替換為自己的設計,進而將任意程式碼插入您的版本。您可以將任何依附元件複製到您控管的伺服器,並阻止建構系統存取 Maven Central 等第三方構件存放區,藉此緩解這兩個問題。但這些鏡像需要花費心力和資源來維護,因此是否使用鏡像,通常取決於專案規模。此外,安全性問題也 藉由要求每個 Pod 的雜湊值 來源存放區中指定的第三方構件導致建構作業 就會失敗。另一個完全避開這個問題的做法,就是將專案的依附元件外包。專案 容器會整合所有依附元件 和 專案原始碼或二進位檔。這實際上表示所有專案的外部依附元件都會轉換為內部依附元件。Google 在內部使用這種方法,檢查 Google 中引用的每個第三方程式庫,並將其放入 Google 來源樹根目錄中的 third_party 目錄。不過,這種做法只適用於 Google,因為 Google 的來源控管系統是專門用於處理極大型單一存放區,因此供應商可能不是所有機構的選項。