以構件為基礎的建構系統

本頁面說明以成果為基礎的建構系統,以及其建構過程的理念。Bazel 是一種以成果為基礎的建構系統。雖然以工作為基礎的建構系統是建構指令碼的最佳步驟,但他們可定義自己的工作,為個別工程師提供過多電力。

以成果為基礎的建構系統小部分是由系統定義的工作,工程師可以以有限的方式設定。工程師仍會告知系統要建構的「內容」,但建構系統會決定建構這個機制的方式。與以工作為基礎的建構系統一樣,以 Artifact 為基礎的建構系統 (例如 Bazel) 仍然擁有建構檔案,但這些建構檔的內容截然不同。Bazel 中的指令碼檔案指的是一種完整的指令碼語言,專門說明如何產生輸出內容。有限的選項會影響影響其建構方式。當工程師使用指令列執行 bazel 時,他們會指定一組要建構的目標 (也就是),Bazel 則負責設定、執行及排程編譯步驟 (方法)。由於建構系統現在能完全控管執行工具的時機,因此能更可靠地保證其更有效率,同時保證仍可確保準確度。

實用功能

依據構件建構建構系統和功能程式設計, 傳統的命令式程式設計語言 (例如 Java、C 和 Python) 會指定要執行陳述式的陳述式清單,就像以工作為基礎的建構系統可讓程式設計師定義一系列步驟{ 101}執行。而函式語言 (例如 haskell 和 ML) 則與一系列數學方程式更類似。以函式語言來說,程式設計師會說明要執行運算,但會詳細說明對編譯器執行運算的時間和方式。

這與在以成果為基礎的建構系統中宣告資訊清單的概念,以及讓系統瞭解如何執行建構作業。許多問題無法透過功能程式設計輕鬆表達,但最主要的好處是:語言通常能將平行的節目進行三角形,並保證其正確性不可使用 弱的語言。如要展現功能性程式設計,最簡單的方法是直接使用一組規則或函式,將一系列資料轉換為另一個資料。這正是建構系統的意義:整個系統實際上就是一個數學函式,可將來源檔案 (和編譯器等工具) 當做輸入內容,並產生二進位檔做為輸出。因此,沒想到在功能規劃的 10 個基礎上建構系統是很好的做法。

瞭解以構件為基礎的建構系統

Google 的建構系統 Blaze 是第一個採用成果的建構系統, Bazel 是 Blaze 的開放原始碼版本。

以下是 Bazel 中的建構檔案 (通常稱為 BUILD) 外觀:

java_binary(
    name = "MyBinary",
    srcs = ["MyBinary.java"],
    deps = [
        ":mylib",
    ],
)
java_library(
    name = "mylib",
    srcs = ["MyLibrary.java", "MyHelper.java"],
    visibility = ["//java/com/example/myproduct:__subpackages__"],
    deps = [
        "//java/com/example/common",
        "//java/com/example/myproduct/otherlib",
    ],
)

在 Bazel 中,BUILD 檔案會定義目標,此處類型的兩個目標為 java_binaryjava_library。每個目標會對應到系統可建立的構件:二進位檔目標會產生可直接執行的二進位檔,程式庫目標則會產生供二進位檔或其他程式庫使用的程式庫。每個指定目標都包含:

  • name:在指令列和其他目標上如何參照目標
  • srcs:要編譯為目標建立成果的來源檔案
  • deps:建立至這個目標前必須建立的其他目標

依附元件可以是同一個套件 (例如MyBinary依賴性:mylib),或位於相同來源階層的其他套裝方案 (例如mylib的依附元件//java/com/example/common)。

與使用工作建構系統的情況相同,您可以使用 Bazel 的指令列工具執行建構作業。如要建立 MyBinary 目標,您必須執行 bazel build :MyBinary。在乾淨的存放區中首次輸入該指令後,Bazel:

  1. 剖析工作區中的每個 BUILD 檔案,以建立構件之間的依附元件。
  2. 使用圖表來判斷MyBinary;也就是說MyBinary取決於這些目標取決於哪些目標。
  3. 依序建構每個依附元件。Bazel 會先建立沒有其他依附元件的目標,並追蹤各個目標的依附元件。一旦目標的所有依附元件都建構完畢,Bazel 就會開始建立該目標。此程序程序會持續進行,直到 MyBinary 的每項相依性依附元件都已建構完成為止。
  4. 建構 MyBinary 以產生最終的二進位檔,而這些二進位檔會連結到步驟 3 建立的所有依附元件。

基本上,此處的情況似乎與使用工作型建構系統時的情況截然不同。實際上,最終結果是相同的二進位檔,產生的生產過程會分析一系列步驟來找出依附元件,然後依序執行這些步驟。但其中有幾項重大差異。第 1 個步驟會出現在步驟 3:因為 Bazel 知道每個目標只會產生 Java 程式庫,因此只會知道執行 Java 編譯器,而不執行任何使用者定義的指令碼。 ,以確定的方式平行執行這些步驟。 相較於在多核心機器上一次建立目標,這可大幅改善建構效能,而且僅是基於以成果為基礎的方法會導致建構系統自行執行執行。策略,以便加強對平行處理的能力。

然而,優點不僅止於平行處理。此方式的下一方法,可讓開發人員在不進行任何變更的情況下,再次執行 bazel build :MyBinary 動作,並且會在不到一秒內關閉,並且在訊息中指出目標為最新版本的 Google Ads 新帳戶重新申請驗證。這或許是因為我們先前討論的功能性程式設計範例才行,但 Bazel 知道每個目標都只有執行 Java 編譯器的結果,而且知道 Java 編譯器的輸出僅取決於但只要輸入並未變更,就能重複使用輸出內容。 這項分析適用於所有層級;當 MyBinary.java 變更時,Bazel 知道要重新建構 MyBinary,但重複使用 mylib。如果 //java/com/example/common 的來源檔案發生變更,Bazel 知道要重新建立該程式庫、mylibMyBinary,但重複使用 //java/com/example/myproduct/otherlib。由於 Bazel 知道在每個步驟執行的工具屬性,因此每次都能重新建構最低構件,同時保證不會產生過時的建構。

相較於構件,而非使用工作來重新建構建構程序,卻很複雜,但功能非常強大。透過減少對程式設計師發布的彈性,建構系統可進一步掌握建構中每個步驟正在執行的工作。並利用這些知識平行處理建構程序,並重複使用輸出結果,藉此提升建構的效率。但這只是第一步,而這些平行處理的構成要素,也是分散式及可擴充建構系統的基礎,

其他優質的 Bazel 秘訣

以構件為基礎的建構系統基本上可解決平行處理問題,以及工作型建構系統中固有的重複使用問題。但我們還是有一些問題尚未解決。Bazel 有能力解決這些問題,所以在繼續操作之前,我們應該先討論這些問題。

做為依附元件

我們先前遇到的一個問題,就是建構在機器上安裝的工具。由於不同的工具版本或位置,在各種系統上重現版本可能並不容易。如果專案使用的語言取決於其建構或編譯平台 (例如 Windows 與 Linux),且每個平台使用的語言不同,那麼問題會更加困難平台需要一套不同的工具來執行相同的工作。

Bazel 會將工具視為每個目標的依附元件,藉此解決這個問題。工作區中的每一個 java_library 都取決於 Java 編譯器,而該編譯器預設為知名的編譯器。每當 Bazel 建立 java_library 時,系統會檢查指定編譯器是否位於已知位置。就像任何其他依附元件一樣,如果 Java 編譯器有所變更,則依賴此依附元件的每個構件都會重新建立。

Bazel 透過設定建構設定來解決問題的第二部分:平台獨立性。這類工具會根據設定類型,而不是直接指定目標:

  • 主機設定:在建構期間執行的工具
  • 目標設定:建構最終要求的二進位檔

擴充建構系統

Bazel 提供許多熱門程式設計語言適用的目標,但工程師當然希望這麼做。「以工作為基礎的系統」的優點之一,就是能支援各種類型的建構程序。最好不要在以成果為基礎的建構系統中來執行。所幸 Bazel 允許透過新增自訂規則來擴充支援的目標類型,

如要定義 Bazel 的規則,規則作者會宣告規則所需的輸入內容 (以 BUILD 檔案傳遞的屬性形式) 和規則產生的固定輸出內容組合。還能定義該規則所產生的動作。每項動作都會宣告其輸入和輸出內容、執行特定執行檔,或是將特定字串寫入檔案,並且可以透過輸入和輸出項目連線至其他動作。這表示動作是建構系統中最低層級的可組合單元,只要動作只使用已宣告的輸入內容和輸出內容,即可能執行任何動作,且 Bazel 會負責進行排程並視情況快取結果。

系統未做到滴水不漏,因為任何人都無法採取特定行動,例如在其中導入非確定性程序等等。但這種情況並不會常在實務上發生,而濫用濫用行為的可能性會大大降低。支援多種常用語言與工具的規則已全面開放線上使用,大多數專案不需要自行定義規則。即使有這些需求,也只須在存放區的集中位置定義規則定義,也就是說,多數工程師不需要使用這些規則,就能使用這些規則。

隔離環境

這些動作看起來就像其他系統中的工作一樣,也可能是寫入並寫入相同檔案,最後又之間出現衝突的情形嗎?實際上,Bazel 會使用沙箱機制避免這些衝突。在支援的系統中,每個動作都是透過檔案系統沙箱來完成該動作。實際上,每個動作只能看到受限制的檔案系統,當中包含已宣告的輸入及其產生的輸出內容。這種機制是由 Linux 等 LXC 等系統強制執行。這表示這些動作無法彼此衝突,因為使用者無法讀取任何未宣告的檔案,而寫入的檔案未宣告,系統會將所有動作捨棄。 完成。Bazel 也會使用沙箱來限制動作無法透過網路通訊。

決定外部依附元件

還有一個問題:建構系統通常需要從外部來源下載依附元件 (無論工具或程式庫),而不是直接建構。在範例中,您可以透過 @com_google_common_guava_guava//jar 依附元件查看這種情況,而依附元件會從 Maven 下載 JAR 檔案。

根據目前工作區以外的檔案而導致風險。這些檔案隨時可能變動,因此可能需要讓建構系統持續檢查其是否為最新版本。如果遠端檔案在工作區原始碼中沒有相對應的變更,則可能導致該版本發生無法重現的建構作業。建構作業可能會在一天內執行,且因為不明原因而造成不明原因失敗。依附元件變更。最後,外部依附元件可能會在第三方擁有時造成重大安全性風險。如果攻擊者能夠入侵該第三方伺服器,就可以將依附元件檔案替換成 {101 }本身的設計,可能會完全掌控您的建構環境及其輸出內容。

最基本的問題是,我們希望建構系統能夠瞭解這些檔案,而不需要檢查這些原始碼。更新依附元件時應秉持著謹慎的選擇,但應集中選擇一處,而非由個別工程師管理,或是由系統自動化。這是因為即使為「Live in Head」模型,我們仍然希望建構確定性,這表示如果您查看上週的修訂版本,應該就會看到依附元件在比現在更多。

Bazel 與其他一些建構系統都需要工作區範圍資訊清單檔案,列出工作區中所有外部依附元件的密碼編譯雜湊值,藉以解決這個問題。雜湊是無法表達檔案的確切方式,因此不會檢查整個檔案至來源控制項。從工作區參照新的外部依附元件時,該依附元件的雜湊會手動或自動新增至資訊清單。Bazel 執行建構作業時,會根據快取定義的預期雜湊值檢查其快取依附元件的實際雜湊值,並只會在雜湊不同時重新下載檔案。

如果我們下載的構件與資訊清單中列出的宣告不同,則除非資訊清單中的雜湊已更新,否則建構將會失敗。這項作業可由系統自動完成,但變更必須經過核准並通過來源控管,才會被建構作業接受新的依附元件。這表示系統一律會更新依附元件的更新時間,如果工作區來源的相應變更,外部依附元件就無法變更。這也表示,當您查看較舊版本的原始碼時,該版本保證會使用當時登入該版本當時使用的相同依附元件 (或失敗)。這些依附元件已無法使用)。

當然,如果遠端伺服器無法使用或開始提供毀損的資料,問題可能仍會有問題,而且如果沒有其他依附元件,這會導致您的所有建構作業開始失敗。為避免這個問題,建議為所有非專案專案建立所有依附元件,並將其對應至您信任和控管的伺服器或服務。否則,即使勾選的雜湊機制保證安全性,您仍然必須取得第三方合作夥伴的信賴。