翻閱先前的頁面時,有一個主題不斷重複: 管理 您自有的程式碼相當簡單,但管理其依附元件很 變得更困難依附元件有很多種:有時候 對特定工作的依賴 (例如「在將版本標示為版本前推送說明文件」 有時也會有依附關係 (例如「我需要 才能使用最新版的電腦視覺資料庫來建構程式碼」)。 有時,您有程式碼集的另一部分有內部依附元件,且 有時候,會對其他團隊擁有的程式碼或資料,造成外部依賴 (貴機構或第三方皆可)。但無論如何 我必須先瞭解事情才能做這個」」的任務,我會重複發生在 以及管理依附元件 是建構系統的基本工作
處理模組和依附元件
使用 Bazel 等構件型建構系統的專案會拆分成一組
模組,以及透過 BUILD
表示依附元件的模組
檔案。妥善整理這些模組和依附元件
對建構系統效能以及需要的工作量
維護。
使用精細模組和 1:1:1 規則
建構以構件為基礎的版本時,第一個想出的問題是
決定個別模組應包含多少功能。在 Bazel 中
模組是以指定可建構單元為目標,
java_library
或 go_binary
。例如,整項專案
將一個 BUILD
檔案放在根目錄,然後
以遞迴方式記錄專案的所有來源檔案。另一端
不過,幾乎每個來源檔案都能加入各自的模組,有效
要求每個檔案都列在 BUILD
檔案中,並依附於其他所有依附的檔案。
大多數專案介於這些極端之間
在效能和可維護性之間取得平衡針對
這可能表示您完全不需要輕觸 BUILD
檔案,
但這意味著建構系統必須
建議您一律同時建構整個專案這表示它無法並行處理或分發建構作業的部分,也無法快取已建構的部分。每個檔案一個模組則相反:建構系統
對建構在快取和排程步驟方面擁有最大的彈性,但
因此工程師必須投入更多心力維護依附元件清單
就會變更檔案參照
雖然精確的精細程度因語言而異 (甚至在同一種語言內也可能不同),但 Google 傾向於採用比以任務為基礎的建構系統中通常編寫的模組更小得多的模組。Google 的一般正式版二進位檔通常會依賴數萬個目標,即使是規模中等的團隊,其程式碼庫中也可能有數百個目標。對於 Java 等具有強大內建封裝概念的語言,每個目錄通常都包含單一套件、目標和 BUILD
檔案 (Pants 是另一個以 Bazel 為基礎的建構系統,稱此為 1:1:1 規則)。包裝較弱的語言
慣例通常會為每個 BUILD
檔案定義多個目標。
較小的建構目標帶來的好處會在規模上開始顯現,因為這類目標可加快分散式建構作業,且不需經常重建目標。測試輸入圖片後,優點更是更吸引人。
目標越精細,建構系統就能變得更聰明
只執行一部分可能受特定指定影響的測試
變更。Google 相信使用較小目標可帶來系統性效益,因此我們已投資開發工具來自動管理 BUILD
檔案,以免造成開發人員負擔,並在一定程度上減輕了使用較小目標的缺點。
其中一些工具 (例如 buildifier
和 buildozer
) 可透過
執行 Bazel 作業的
buildtools
目錄。
最小化模組顯示設定
Bazel 和其他建構系統允許每個目標指定可見度,這是一個屬性,可決定其他目標是否會依附於該目標。私人目標
只能在自己的 BUILD
檔案中參照。目標可以將更廣泛的瀏覽權限授予明確定義的 BUILD
檔案清單目標,或是在公開瀏覽權限的情況下,將瀏覽權限授予工作區中的每個目標。
如同大多數程式設計語言,盡可能減少可見度通常是最佳做法。一般來說,Google 團隊只會將目標設為公開,如果這些目標代表 Google 任何團隊都可使用的廣泛使用的程式庫。如果團隊要求其他人必須先與他們協調,才能使用他們的程式碼,就會將客戶目標列入許可清單,以便設定目標的顯示設定。每項
團隊的內部實作目標只能在目錄內
由這個團隊所擁有,而大部分的 BUILD
個檔案只會有一個排除目標
私人。
管理依附元件
模組必須能夠彼此參照。將 Pod 名稱
建構到精細模組中,藉此管理依附元件
這些模組之間 (不過工具可以協助自動化這項作業)。表示這些依附元件通常會成為 BUILD
檔案中的主要內容。
內部依附元件
在大型專案中,如果已細分為精細的模組,則大多數依附元件可能為內部依附元件,也就是在相同來源存放區中定義及建構的其他目標。內部依附元件與外部依附元件的差異在於,內部依附元件是從來源建構而成,而非在執行建構作業時以預先建構的構件形式下載。這也表示 內部依附元件—目標及其所有內部依附元件一直都是 以存放區的相同修訂版本/修訂版本為基礎建構而成在處理內部依附元件時,必須謹慎處理的一個問題,就是如何處理傳遞依附元件 (圖 1)。假設目標 A 依附於目標 B,而目標 B 依附於共用程式庫目標 C。目標 A 應該可以使用類別 目標 C 所定義?
圖 1. 遞移依附元件
如果對基礎工具有疑慮,這並沒有問題;兩者皆是 建構時,B 和 C 會連結至目標 A,也就是說, A 是 A 這個名詞。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
自動代管的依附元件對於小型專案是便捷的,但 往往是一種災難因應做法, 目前是由多位工程師合作自動管理的依附元件問題在於,您無法控制版本更新時間。目前沒有任何方法能確保外部方不會造成破壞 更新 (即使他們聲稱使用語意版本管理),因此建構 隔天工作可能會分崩離析 或復原為工作狀態即使建構作業沒有中斷,也可能發生難以追蹤的細微行為或效能變更。
相較之下,由於手動管理的依附元件需要變更來源控管,因此可以輕鬆發現及還原,而且可以檢查較舊版本的存放區,以便使用較舊的依附元件進行建構。Bazel 規定所有依附元件的版本都必須手動指定。即使規模適中,手動版本管理的額外負擔也值得,因為這能帶來穩定性。
單一版本規則
不同版本的程式庫通常會以不同的構件表示,因此理論上,同一個外部依附元件的不同版本,不應在建構系統中以不同的名稱宣告。這樣一來,每個目標都可以選擇要使用的依附元件版本。這會在實際操作中造成許多問題,因此 Google 會對程式碼庫中的所有第三方依附元件嚴格執行單一版本規則。
要允許多個版本的最大問題在於鑽石依賴性 問題。假設目標 A 依附於目標 B 和外部第 1 版 資源庫。如果目標 B 之後經過重構,以便在 v2 上新增依附元件 目標 A 將會破壞,因為現在其隱含仰賴兩個 同一個程式庫的不同版本從目標新增依附元件至具有多個版本的任何第三方程式庫,從實務角度來看,這絕非安全的做法,因為目標的任何使用者都可能已依附於其他版本。遵循「單一版本規則」可避免發生這種衝突,如果目標在第三方程式庫中新增依附元件,任何現有的依附元件都會位於該版本,因此可以順利共存。
遞移外部依附元件
處理外部依附元件的傳遞依附元件可能特別困難。許多構件存放區 (例如 Maven Central) 都允許構件指定存放區中其他構件的特定版本依附元件。Maven 或 Gradle 等建構工具通常會以遞迴方式下載 預設遞移依附元件,意味著在 可能會導致大量下載構件 。
這種做法可說是非常方便:在新的程式庫中新增依附元件時 必須逐一追蹤程式庫的遞移依附元件 並手動新增也有一大缺點: 程式庫可以依附同一個第三方程式庫的不同版本, 策略一定違反一版規則,而且會導向鑽石級 依附元件問題如果您的目標仰賴使用 同一依附元件的不同版本,系統不會告訴您 get。這也表示,如果新版本開始擷取部分依附元件的衝突版本,更新外部依附元件可能會導致整個程式碼集出現看似不相關的失敗情形。
因此,Bazel 不會自動下載遞移依附元件。
很遺憾,目前沒有萬靈丹,Bazel 的替代方案是要求全域檔案,列出存放區的每個外部依附元件,以及在整個存放區中用於該依附元件的明確版本。幸好,Bazel 提供的工具能夠自動
產生這類檔案,當中包含一組 Maven 的遞移依附元件
導致學習失真性您可以執行這項工具一次,為專案產生初始 WORKSPACE
檔案,然後手動更新該檔案,以調整各依附元件的版本。
這裡的選擇又是便利性和可擴充性之間的抉擇。S 號 因此,專案可能不需費心管理遞移依附元件 並可能利用自動遞移性推動 依附元件隨著組織和程式碼庫規模的擴大,這項策略的吸引力也越來越低,衝突和意外結果的發生頻率也越來越高。採用更大規模的架構時,手動管理依附元件的成本大不相同 低於自動依附元件導致的問題處理成本 以自動化做法管理成本
使用外部依附元件快取建構結果
外部依附元件通常是由第三方提供,這些第三方會發布穩定版的程式庫,但可能不會提供原始碼。部分機構也可能選擇將部分自有程式碼做為構件提供,讓其他程式碼片段可依附這些構件,而非內部依附元件。如果構件建構速度較慢,但下載速度較快,這項做法理論上可加快建構速度。
不過,這也會帶來大量額外負擔和複雜性:有人需要負責建構每個構件,並將其上傳至構件存放區,而用戶端也需要確保能隨時取得最新版本。由於系統的不同部分會從存放區的不同位置建構,且來源樹狀結構不再一致,因此偵錯作業也變得更加困難。
想要解決長時間建構的構件問題 如同前文所述,使用支援遠端快取的建構系統。如此 建構系統會將每次建構作業產生的構件儲存至位置 因此,如果開發人員依賴 模型會自動下載 不必實際建構這可提供直接依附成果物所帶來的所有效能優勢,同時確保建構作業與從相同來源建構的作業一樣一致。這是 Google 內部使用的策略,您可以設定 Bazel 使用遠端快取。
外部依附元件的安全性和可靠性
根據第三方來源的構件,可能會有風險。還有
第三方來源 (例如構件存放區) 出現可用性風險
如果無法下載,整個版本可能會停滯不前
外部依附元件可能會有安全風險:若第三方系統
完全被攻擊者入侵,攻擊者就可以
可加入自行設計的構件
融入建構作業您可以將任何依附元件複製到您控管的伺服器,並阻止建構系統存取 Maven Central 等第三方構件存放區,藉此緩解這兩個問題。但這些鏡像需要花費心力和資源來維護,因此是否使用鏡像,通常取決於專案規模。您也可以要求在原始碼存放區中指定每個第三方構件的雜湊,藉此完全避免安全性問題,且不必付出太多成本,因為如果構件遭到竄改,這麼做就會導致建構作業失敗。另一種完全的替代方案
而問題是廠商專案的依附元件。當專案供應商提供其依附元件時,會將依附元件與專案的原始碼一起納入原始碼控制,以原始碼或二進位檔的形式。這實際上意味著
專案的所有外部依附元件都會轉換為內部依附元件
依附元件Google 內部會使用這種做法,檢查每個第三方
整個 Google 參照的程式庫,並編入根目錄的 third_party
目錄中
這個開放原始碼專案的主要工具不過,這種做法只適用於 Google,因為 Google 的來源控管系統是專門用於處理極大型單一存放區,因此供應商可能不是所有機構的選項。