Bazel 程式碼基底

回報問題 查看原始碼 夜間 7.2 7.1 7.0 6.5 6.4

本文件將說明程式碼集以及 Bazel 的結構。這項服務 設計目標在於願意為 Bazel 做出貢獻的人員,而非使用者。

簡介

Bazel 的程式碼集相當大 (正式環境程式碼約需 350 KLOC 及約 260 KLOC 測試) 也完全不熟悉整個作業環境,大家都知道 特定山谷,但只有少數人知道每一座山 往上移動即可

為了避免在旅途中途中無法找到自己, 森林昏暗的通道迷路了,這份文件試圖 概略介紹程式碼集,更便於上手 處理這個問題

Bazel 的原始碼公開版本存放在 GitHub github.com/bazelbuild/bazel這並不是 「可靠資料來源」是取自 Google 內部來源樹狀結構 含有非 Google 用途的額外功能。 長期目標是將 GitHub 做為可靠資料來源。

使用者可以透過一般 GitHub 提取要求機制接受捐款, 由 Google 員工手動匯入內部來源樹狀結構, 並重新匯出至 GitHub。

用戶端/伺服器架構

Bazel 的大部分內容位於伺服器程序,該程序可留在建構之間的 RAM 中。 如此一來,Bazel 就能維持建構作業之間的狀態。

這也是 Bazel 指令列有兩種選項的原因:啟動和 指令在指令列中:

    bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar

某些選項 (--host_jvm_args=) 位於要執行的指令名稱之前 有些晚於 (-c opt);先前的類型稱為「啟動選項」和 會影響伺服器程序的整體狀態,後者的「指令」 Option"),則只會影響單一指令。

每個伺服器執行個體都有一個相關聯的來源樹狀結構 (「工作區」) 工作區通常有一個有效的伺服器執行個體。系統可能會 指定自訂輸出基礎 (詳情請參閱「目錄版面配置」一節 資訊)。

Bazel 發布的是單一 ELF 執行檔,同時也是有效的 .zip 檔案。 輸入 bazel 時,上述 ELF 執行檔會在 C++ 中實作 ( 「用戶端」) 會成為掌控度。程式碼會使用 步驟如下:

  1. 檢查是否已自行擷取。如果不是,就完成了這個 是伺服器的實作來源。
  2. 檢查運作中的伺服器執行個體是否正常運作:運作中、 並具備正確的啟動選項,並使用正確的工作區目錄。這項服務 查看 $OUTPUT_BASE/server 目錄,即可找出執行中的伺服器 當中包含鎖定檔案,其中包含伺服器監聽的通訊埠。
  3. 如有需要,您可以終止舊的伺服器程序
  4. 如有需要,請啟動新的伺服器程序

合適的伺服器程序準備就緒後,需要執行的指令為 並透過 gRPC 介面讓 Bazel 輸出, 再連線至終端機一次只能執行一項指令。這是 透過精心設計的鎖定機制實作,包括 C++ 中的部分和 Java。某些基礎架構可平行執行多個指令 因為 bazel version 無法與其他指令同時執行 有點難堪主要阻礙因素是 BlazeModule 的生命週期 以及 BlazeRuntime 中的某個狀態

Bazel 伺服器會在指令結束時,將結束代碼傳送至用戶端 應該傳回。有趣的是 bazel run 的實作方式: 這項指令負責執行 Bazel 才建構的項目,但無法執行這項動作 沒有終端機反而會顯示 而用戶端應該使用哪個二進位檔?

當按下 Ctrl-C 鍵時,用戶端會將該呼叫轉譯為 gRPC 上的取消呼叫 連線狀態,將盡快終止指令。在 第三個 Ctrl-C 鍵,用戶端會改為將 SIGKILL 傳送至伺服器。

用戶端的原始碼位於 src/main/cpp 下方 在 src/main/protobuf/command_server.proto 中與伺服器通訊,

伺服器的主要進入點是 BlazeRuntime.main(),gRPC 呼叫 由 GrpcServerImpl.run() 處理。

目錄版面配置

Bazel 在建構過程中會建立一組較為複雜的目錄。滿載 如需說明,請參閱輸出目錄版面配置

「工作區」就是執行 Bazel 的來源樹狀結構這個 ID 通常會對應至 您對原始碼控管內容的做法

Bazel 會將所有資料放到「輸出使用者根目錄」下。這通常 $HOME/.cache/bazel/_bazel_${USER},但可使用 --output_user_root 啟動選項。

「安裝數」就是擷取 Bazel 的地方系統會自動完成這項作業 每個 Bazel 版本都會根據其下一次檢查碼 安裝數。電子郵件地址預設為 $OUTPUT_USER_ROOT/install,但可以變更 使用 --install_base 指令列選項

「輸出基礎」是 Bazel 執行個體附加至特定容器的位置 寫入工作區每個輸出基礎最多只能有一個 Bazel 伺服器執行個體 隨時保持運作通常位於 $OUTPUT_USER_ROOT/<checksum of the path to the workspace>。您可以透過 --output_base 啟動選項變更這項設定。 協助您擺脫僅侷限在 您隨時都能在任何工作區中執行 Bazel 執行個體。

輸出目錄包含下列項目:

  • $OUTPUT_BASE/external 擷取的外部存放區。
  • exec 根目錄,包含所有來源的符號連結 目前版本的程式碼它位於$OUTPUT_BASE/execroot,過程中 建構,工作目錄為 $EXECROOT/<name of main repository>。雖然這是$EXECROOT 因為變更不相容,無法長期討論
  • 在建構期間建立的檔案。

執行指令的程序

一旦 Bazel 伺服器取得控制權並收到需要執行指令的指令後 執行時,事件的順序如下:

  1. 系統已通知 BlazeCommandDispatcher 有新的要求。由此決定 判斷指令是否需要工作區執行 (幾乎所有指令,但 適用於無需使用原始碼的元件 help) 以及是否有其他指令正在執行。

  2. 已找到正確的指令。每個指令都必須實作 BlazeCommand,且必須包含 @Command 註解 ( 反模式,如果指令需要所有中繼資料 (按 BlazeCommand 的方法描述)

  3. 系統會剖析指令列選項,每個指令都有不同的指令列 選項,詳情請參閱 @Command 註解。

  4. 已建立事件匯流排。事件匯流排是發生事件的串流 建構期間其中部分資源會在 Bazel 外部 建構事件通訊協定,向全世界說明 好極了!

  5. 這個指令可以取得控制權最有趣的指令是執行 建構、測試、執行、涵蓋率等 由 BuildTool 導入。

  6. 系統會剖析指令列上的目標模式組合,並使用類似萬用字元 (例如 已解決 //pkg:all//pkg/...。這會在 AnalysisPhaseRunner.evaluateTargetPatterns() 並在 SkyFrame 中重新改寫為 TargetPatternPhaseValue

  7. 系統會執行載入/分析階段來產生動作圖 (導向 需要為建構執行的指令的非循環圖)。

  8. 執行階段會執行。這表示必須執行各項必要的操作 建構要求的頂層目標執行。

指令列選項

如要瞭解 Bazel 叫用的指令列選項,請參閱 OptionsParsingResult 物件,而這個物件又包含來自「選項」的地圖 類別"設為選項的值「選項類別」是 OptionsBase,並將相關的指令列選項分組 其他。例如:

  1. 程式設計語言 (CppOptionsJavaOptions) 的相關選項。 這些應為 FragmentOptions 的子類別,而且最終會進行包裝 複製到 BuildOptions 物件中
  2. 與 Bazel 執行動作的方式 (ExecutionOptions) 相關的選項

這些選項設計在分析階段可用,且 透過 Java 中的 RuleContext.getFragment() 或 Starlark 中的 ctx.fragments)。 其中有些類型 (例如是否要執行 C++ include 掃描) 但這一律需要明確的配管 「BuildConfiguration」目前缺貨。詳情請參閱 「設定」部分

警告:我們想假設 OptionsBase 執行個體無法變更, 但可以透過這種方式使用 (例如 SkyKeys 的部分功能)。但實際上並非如此 修改 Pod 是讓 Bazel 以細微的方式破壞 Bazel 的好方法 即可偵錯。可惜的是,這些變數實際上不可變更是相當重大的努力。 (在建構完成後立即修改 FragmentOptionsequals()hashCode() 之前 可呼叫)

Bazel 透過下列方式學習選項類別:

  1. 有些則是硬式編碼到 Bazel (CommonCommandOptions) 中
  2. 透過每個 Bazel 指令的 @Command 註解
  3. ConfiguredRuleClassProvider 開始 (與指令列選項相關 到個別程式設計語言)
  4. Starlark 規則也可以定義自己的選項 (詳情請參閱 請按這裡)

每個選項 (不含 Starlark 定義選項) 都是 具有 @Option 註解的 FragmentOptions 子類別, 指令列選項的名稱和類型,以及一些說明文字。

指令列選項值的 Java 類型通常 (字串、整數、布林值、標籤等)。不過,我們也提供 其他較複雜的類型在此情況下,轉換程序會從 屬於資料類型的 com.google.devtools.common.options.Converter

Bazel 所看到的來源樹狀結構

Bazel 負責建構軟體 或是解讀原始碼Bazel 在各原始碼上 稱為「工作區」存放區、套件 不過,編寫這類演算法並不容易 因為我們無法寫出所有可能的規則

存放區

「存放區」是開發人員處理的原始碼樹狀結構這通常 代表單一專案Bazel 的祖系 Blaze,在單聲道存放區上運作 也就是單一原始碼樹狀結構,其中包含用於執行建構的所有原始碼。 相較之下,Bazel 支援原始碼橫跨多個 與存放區叫用 Bazel 的存放區稱為 其他存放區則稱為「外部存放區」。

存放區是以名為 WORKSPACE (或 WORKSPACE.bazel) 的檔案在 複製到根目錄這個檔案含有「全域」資訊到 例如一組可用的外部存放區運作方式就像是 一般的 Starlark 檔案,因此一個可以另外 load() 個 Starlark 檔案。 通常會用來提取存放區所需的存放區 明確參照 (我們稱之為「deps.bzl 模式」)

外部存放區的代碼已相互連結,或由其下載 $OUTPUT_BASE/external

執行建構時,必須拼湊整個來源樹狀結構。本 SymlinkForest 將主要存放區中的每個套件符號連結 $EXECROOT,並將每個外部存放區複製到 $EXECROOT/external$EXECROOT/.. (第一個是常規,因此不可能含有包裹 在主要存放區中稱為 external這就是為什麼我們要改用 )

套件

每個存放區都包含套件、相關檔案 依附元件規格這些元件是由名為 BUILDBUILD.bazel。如果兩者存在,Bazel 偏好使用 BUILD.bazel;原因 為什麼 Bazel 的祖系 Blaze 仍被接受 BUILD 檔案 檔案名稱。後來是一些常見的路徑區隔 Windows 上的檔案,檔案名稱不區分大小寫。

套件彼此獨立:套件的 BUILD 檔案變更 無法造成其他套件變更。新增或移除 BUILD 個檔案 _可以_變更其他套件,因為遞迴 glob 在套件邊界時會停止 如此,如果有 BUILD 檔案,系統就會停止遞迴作業。

BUILD 檔案的評估作業稱為「套件載入」。導入方式 PackageFactory 類別中的工作,就是呼叫 Starlark 翻譯器和 必須瞭解可用的規則類別組合。套件的結果 載入是 Package 物件。大部分是來自字串 ( 目標) 傳送給目標本身。

封裝套件載入過程中有許多複雜複雜性:Bazel 不會 要求每個來源檔案都必須明確列出,改為執行 glob (例如 glob(["**/*.java"]))。與 Shell 不同,它支援遞迴 glob 下層是子目錄 (而不是子套件)。這需要下列權限: 因為這可能會拖慢運作速度,因此我們必須運用各種技巧 盡可能平行且更有效率

以下類別會實作繪圖操作:

  • LegacyGlobber,這是快速且模糊不清的空中景 Globber
  • SkyframeHybridGlobber,這個版本使用 SkyFrame,並且會還原為 舊版的 globber,以免發生「SkyFrame 重新啟動」(如下所述)

Package 類別本身包含一些專門用於 剖析 WORKSPACE 檔案,何者對實際套件不合理。這是 因為描述一般套件的物件不應含有 描述其他內容的欄位包括:

  • 存放區對應項目
  • 已註冊的工具鍊
  • 已註冊的執行平台

在理想情況下,從本機剖析 WORKSPACE 檔案 剖析一般套件,讓 Package不需配合需求 兩者當中這並不容易,因為兩者結合在一起 就像這樣

標籤、指定目標和規則

套件是由目標組成,目標類型如下:

  1. 「Files」:建構的輸入或輸出內容。於 Bazel 比較稱稱為「Artifacts」 (我們在其他地方會討論)。並非 建立檔案是目標輸出值很常見 Bazel 不會有相關聯的標籤。
  2. 規則:以下說明從輸入產生輸出內容的步驟。他們 通常與程式設計語言相關聯,例如 cc_libraryjava_librarypy_library) 中,但有幾個語言不通用 (例如 genrulefilegroup)
  3. 套件群組:請參閱「瀏覽權限」一節。

目標名稱稱為標籤。標籤的語法是 @repo//pac/kage:name,其中 repo 是標籤所在存放區的名稱 位於,pac/kage 是其 BUILD 檔案所在的目錄,name 則是 檔案 (如果標籤是指來源檔案),相對於 套件。在指令列上參照目標時,標籤的部分內容 可以省略:

  1. 如果省略存放區,標籤就會被傳送至主目錄 Cloud Storage 也提供目錄同步處理功能
  2. 如果省略包裹部分 (例如 name:name),則會使用標籤 位於目前工作目錄的套件 (相對路徑) 。

有一種規則 (例如「C++ 程式庫」) 稱為「規則類別」。規則類別可能會 可以在 Starlark (rule() 函式) 中實作,或是在 Java 中實作 (因此稱為 「原生規則」並輸入 RuleClass)長遠來看,每個特定語言 系統會在 Starlark 中執行此規則,但部分舊版規則系列 (例如 Java 規則系列) 目前仍然在 Java 中

必須在 BUILD 個檔案的開頭匯入 Starlark 規則類別 使用 load() 陳述式,Java 規則類別則是「inlyly」已知 Bazel,因為要透過 ConfiguredRuleClassProvider 註冊。

規則類別包含以下資訊:

  1. 其屬性 (例如 srcsdeps):其類型、預設值、 限制條件等
  2. 各屬性所附加的設定轉換和切面 (如有)
  3. 規則的執行方式
  4. 遞移資訊提供者「通常」規則建立

術語附註:在程式碼集中,我們通常會使用「規則」來指定目標 由規則類別建立而成不過在 Starlark 和麵向使用者的說明文件中 「規則」只能用來參照規則類別本身。目標 只是一個「目標」另請注意,雖然 RuleClass 中有「類別」 名稱,規則類別和目標之間沒有 Java 繼承關係 這兩種政策

SkyFrame

Bazel 的基礎評估架構稱為 SkyFrame。模型 在建構期間需要建立的所有事項都組織成 有邊緣指向資料的有邊緣圖形 也就是說,還需要知道其他資料才能建構這些資料

圖表中的節點稱為 SkyValue,其名稱會呼叫 SkyKey 秒。兩者都無法變更請務必只提供不可變動的物件 所有可連線的應用程式這個不變數幾乎總是處於停滯狀態 (例如:BuildOptions 所屬的個別選項類別) 《BuildConfigurationValue》和《SkyKey》) 其實很辛苦 或者只以無法從外部觀察到的方式變更這些變數。 這個步驟會遵循在 SkyFrame 中計算的所有內容 (例如 此外,設定的目標) 也必須無法變更。

觀察 SkyFrame 圖表最方便的方法就是執行 bazel dump --skyframe=detailed,這會傾印圖表,每行一個 SkyValue。最好 因為這類模型可能相當龐大

SkyFrame 位於 com.google.devtools.build.skyframe 套件中。 名稱相似的套件 com.google.devtools.build.lib.skyframe 包含 必須在 SkyFrame 上執行 Bazel。進一步瞭解 SkyFrame 請參閱這裡

為了將指定的 SkyKey 評估為 SkyValue,SkyFrame 會叫用 代表鍵類型的 SkyFunction。在函式的執行期間 評估作業,程式可能會呼叫 SkyFunction.Environment.getValue() 的各種超載。這個引數包含 將這些依附元件登錄到 SkyFrame 內部圖表中時,一定會有副作用 SkyFrame 必須在任何依附元件的依附元件時,才知道要重新評估函式 變更。換句話說,SkyFrame 的快取和漸進式運算工作 SkyFunctionSkyValue 的精細程度。

每當 SkyFunction 要求無法使用的依附元件時,getValue() 會傳回空值這個函式應會依照 傳回空值之後,SkyFrame 會評估 無法使用的依附元件,然後從頭重新啟動函式 (只需執行此操作) 則 getValue() 呼叫將成功完成,且傳回非空值的結果。

此操作的結果是指在 SkyFunction 中執行的任何運算 才能重新啟動但這不包括 評估快取的依附元件 SkyValues。因此,我們通常以 相關內容:

  1. 使用 getValuesAndExceptions() 分批宣告依附元件, 限制重新啟動的次數
  2. SkyValue 拆分為由不同元素計算的獨立部分 SkyFunction,以便獨立運算及快取。這個 因為這可能會增加記憶體 。
  3. 在重新啟動之間儲存狀態,方法為使用 SkyFunction.Environment.getState(),或保留臨時靜態快取 「天空框架背面」。

基本上,我們需要這些類型的解決方法,因為我們經常 數十萬個飛行中的 SkyFrame 節點,而且 Java 不支援 輕量化執行緒

史塔拉克

Starlark 是使用者用來設定及擴充特定網域的語言 Bazel。它被視為 Python 受限部分,其型別較少、類型、 控制流程上的更多限制,最重要的是,高度不變性 保證啟用並行讀取。這並不是圖說完成 勸阻部分 (但非全體) 使用者嘗試 以特定語言執行程式設計工作

Starlark 已在 net.starlark.java 套件中實作。 同時也有獨立的 Go 實作 請按這裡。Java 目前用於 Bazel 的實作是解譯器。

Starlark 用於多種情境,包括:

  1. BUILD 語言。這時系統會定義新規則。Starlark 程式碼 在此情境中運作僅具備 BUILD 檔案內容的存取權 和其載入的 .bzl 檔案。
  2. 規則定義。這是新規則 (例如支援新規則) 語言) 後方。在本情境中執行的 Starlark 程式碼可存取 相關的設定和資料 (詳情請參閱 )。
  3. WORKSPACE 檔案。這就是外部存放區 主要原始來源樹狀結構中已定義)。
  4. 存放區規則定義。這就是新的外部存放區類型的位置 在本環境中運作的 Starlark 程式碼可在 也就是執行 Bazel 的機器,並能存取工作區以外的位置。

BUILD.bzl 檔案適用的方言稍有不同 因為這類訊息表達了不同的內容有差異清單 請按這裡

有關於 Starlark 的詳細資訊 請按這裡

載入/分析階段

在載入/分析階段,Bazel 會決定需要執行哪些動作 請思考如何設計特殊規則基本單元是「已設定的目標」,也就是 理論上也是 (目標、設定) 的組合

這就是所謂的「載入/分析階段」因為它可以分成兩個部分 過去可以序列化,但現在可以重疊

  1. 載入套件,也就是將 BUILD 檔案轉換為 Package 物件 代表這些 Pod 的物件
  2. 分析設定的目標,也就是執行 要產生動作圖表的規則

每個已設定目標都會在所設目標的遞移程序中運作 就必須由下而上分析也就是分葉節點 其次是指令列上的項目要分析的輸入內容 單一設定的目標如下:

  1. 設定。(「如何」建立該規則,例如目標 也提供使用者想使用的指令列選項 傳遞至 C++ 編譯器)
  2. 直接依附元件。他們有遞移資訊供應者 對應至要分析的規則這些呼叫之所以呼叫,是因為它們提供了 「滾動式」將伺服器運送發出的資訊 例如類別路徑上的所有 .jar 檔案,或是 必須連結至 C++ 二進位檔)
  3. 目標本身。這是載入目標套件的結果 。對規則來說,這類屬性包括屬性 因為這一切都很重要
  4. 所設定目標的實作方式。以規則來說,這可以是 它應該位於 Starlark 或是 Java已導入所有非規則設定的目標 。

分析設定的目標後,輸出內容如下:

  1. 設定仰賴其指定目標的遞移資訊提供者可以 存取權
  2. 可建立的構件和產生這些構件的動作。

提供給 Java 規則的 API 為 RuleContext,相當於 Starlark 規則的 ctx 引數。其 API 功能更強大 所以更容易做 Bad ThingsTM 的做法,例如,編寫具有時間或 會使 Bazel 伺服器因 Java 例外狀況或違反不變體 (例如不小心修改了 Options 執行個體,或將已設定的目標設為可變動)

決定所設目標的直接依附性演算法 目前住在DependencyResolver.dependentNodeMap()

設定

設定指的是是建構目標:針對哪些平台 指令列選項等

同一個版本中的多項設定可以建構相同的目標。這個 很實用,舉例來說,如果相同的程式碼用於 我們會進行交叉編譯 建構笨重的 Android 應用程式 (包含適用於多個 CPU 的原生程式碼) 架構)

概念上,設定就是 BuildOptions 例項。不過,在 練習,BuildOptions 會納入 BuildConfiguration,提供 其他功能這會從 依附元件圖如果版本有所異動 重新分析

這會導致異常狀況,比如說,如果 請求的測試執行作業變更次數, 會影響測試目標 (我們打算「修剪」設定, 但尚未準備就緒)。

如果規則實作需要進行某些設定,就必須宣告 它的定義是使用 RuleClass.Builder.requiresConfigurationFragments() ,直接在 Google Cloud 控制台實際操作。這兩者都是避免出錯 (例如使用 Java 片段的 Python 規則),並 有助於減少設定,例如當 Python 選項有所變更時,C++ 使用者不必重新分析

規則的設定不一定會與規則的「父項」設定相同 規則。在依附元件邊緣變更設定的程序稱為 「設定轉換」可能在兩個地方發生:

  1. 依附元件邊緣。這些轉場效果是在 Attribute.Builder.cfg() 且是來自 Rule 的函式 (其中 轉換時) 並將 BuildOptions (原始設定) 轉換為 或多個 BuildOptions (輸出設定)。
  2. 任何連入設定目標的連入邊緣。這些都是 RuleClass.Builder.cfg()

相關類別為 TransitionFactoryConfigurationTransition

因此會使用設定轉換,例如:

  1. 如要宣告會在建構期間使用特定依附元件,以及該依附元件 因此應在執行架構中
  2. 如要宣告必須為多個依附元件建立特定依附元件 架構 (例如適用於笨重的 Android APK 中的原生程式碼)

如果設定轉換會產生多項設定,則稱為 分割轉場效果

您也可以在 Starlark 中實作設定轉換 (說明文件) 請按這裡)

遞移資訊提供者

遞移資訊供應器是設定目標的方法 (而 _only_way) 來告知系統的其他已設定目標。原因 「遞移」是因為名稱通常經過匯總處理 接收所設定目標的遞移性封閉

Java 遞移資訊提供者之間通常會有一對一的通訊 和 Starlark 節點 (DefaultInfoFileProviderFilesToRunProviderRunfilesProvider,因為該 API 一般而言,比起 Java 的直接音譯方式,這種做法會得到更多的 Starlark 內容)。 金鑰為下列其中一項:

  1. Java 類別物件。這個做法僅適用於不支援 可透過 Starlark 存取。這些提供者是 TransitiveInfoProvider
  2. 字串。這屬於舊式,非常不建議您這樣做,因為這麼做很可能導致 名稱衝突這種遞移資訊提供者是 build.lib.packages.Info
  3. 提供者符號。你可以使用 provider() 從 Starlark 建立這項功能 函式,這是建立新的提供者的建議方式。符號是 由 Java 的 Provider.Key 執行個體表示。

在 Java 中實作的新提供者應使用 BuiltinProvider 來實作。 NativeProvider 已淘汰 (我們可能尚未移除) 無法透過 Starlark 存取 TransitiveInfoProvider 子類別。

已設定的目標

設定的目標會實作為 RuleConfiguredTargetFactory。另有 子類別。Starlark 設定的目標 透過 StarlarkRuleConfiguredTargetUtil.buildRule() 建立。

已設定的目標工廠應使用 RuleConfiguredTargetBuilder 來 建構回傳值包含下列項目:

  1. 他們的 filesToBuild,是「這項規則的一組檔案」的朦朧概念 代表。」這些是在設定的目標時建構的檔案 位於指令列或 Genrule 的 srcs 中。
  2. 其執行檔案、一般資料和資料。
  3. 輸出內容群組。這些包含各種不同的「其他檔案組合」此規則可以 建構應用程式如要存取這些屬性,可以使用 檔案群組規則,並在 Java 中使用 OutputGroupInfo 提供者。

執行檔案

某些二進位檔需要資料檔案才能執行。其中一個明顯例子是需要 輸入檔案在 Bazel 中,這會以「執行檔案」的概念表示。A 罩杯 「執行檔案樹狀結構」是特定二進位檔的資料檔案的目錄樹狀結構。 是在檔案系統中建立為具有個別符號連結的符號連結樹 ,指向位於輸出樹狀結構的來源檔案。

一組執行檔案會以 Runfiles 執行個體表示。概念上 從執行檔案樹狀圖中檔案的路徑對應至會呼叫的 Artifact 例項 代表該物件對兩個值而言,這比單一 Map 要稍微複雜 原因:

  • 在大多數情況下,檔案的執行檔路徑與 execpath 相同。 藉此節省一些 RAM。
  • 執行檔案樹狀結構中有各種舊版項目,也需要 。

執行檔案是利用 RunfilesProvider 收集:這個類別的例項 代表所設目標 (例如程式庫) 的執行檔案及其轉換檔案 封閉式需求,而且收集的方式類似巢狀結構 (實際上 透過封面下以巢狀結構方式實作):每個目標會聯集執行檔案 加入一些自己的依附元件,然後將產生的設定 。RunfilesProvider 執行個體包含兩個 Runfiles 一個例子是規則依賴「資料」時屬性和 每種傳入的依附元件都有一個選項這是因為 如果透過資料屬性依賴,有時會顯示不同的執行檔 。這是我們尚未準備好處理的不理想舊版行為 尚未移除。

二進位檔的執行檔案會表示為 RunfilesSupport 的執行個體。這個 與 Runfiles 不同,因為 RunfilesSupport 具備 實際建構的環境不同 (與 Runfiles 不同,後者僅是對應)。這個 需要下列其他元件:

  • 輸入執行檔案資訊清單。這是針對 執行檔案樹狀結構用來當做執行檔案樹狀結構內容的 Proxy 而 Bazel 會假設,只有在 狀態變更
  • 輸出執行檔案資訊清單。會由執行階段程式庫使用 可處理執行檔案樹狀結構,特別是在 Windows 上,它們有時會不支援 符號連結。
  • 執行檔案中間人為了存在執行檔案樹狀結構, 建構符號連結樹狀結構,以及符號連結指向的成果。在訂單中 來減少依附元件邊緣的數量,則可執行 這些數字代表所有可能的意義
  • 執行指令列引數,用來執行執行檔是 RunfilesSupport 物件代表。

切面

切面是用來「在依附關係圖中傳播計算」。這些 Bazel 使用者 請按這裡。不錯 動機範例為通訊協定緩衝區:proto_library 規則不應知道 但需要建立通訊協定的實作方式 透過任何程式編寫緩衝區訊息 (通訊協定緩衝區的「基本單位」) 語言應遵循 proto_library 規則,這樣即使有兩個目標 相同的語言需要使用相同的通訊協定緩衝區,因此只能建構一次。

就像設定的目標一樣,在 SkyFrame 中會以 SkyValue 表示 建構方式也和設定的目標 建構而成:它們擁有名為 ConfiguredAspectFactory 的工廠類別 但與設定的目標工廠不同,RuleContext也能知道 其所連結的設定目標及其供應商

依依附元件圖表向下傳播的切面組合 透過 Attribute.Builder.aspects() 函式管理這項屬性。以下幾方面 參與過程中且名稱容易混淆的類別:

  1. AspectClass 是切面的實作。可以是 Java (如果是子類別) 或 Starlark (在這個例子中是 StarlarkAspectClass 的例項)。這與 RuleConfiguredTargetFactory
  2. AspectDefinition 是切面的定義;其中包含 其提供者、其提供的提供者,並包含 實作效果,例如適當的 AspectClass 例項是 類似 RuleClass
  3. AspectParameters 是一種參數化所延展的切面的方式 依附元件圖這是與字串對應的字串。最佳範例 因此是通訊協定緩衝區的優點:如果語言有多個 API, 和應該針對哪個 API 建立通訊協定緩衝區的資訊 就會向下傳播
  4. Aspect 代表計算某切面所需的所有資料 會向下傳播依附元件圖表這是切面類別 定義和參數
  5. RuleAspect 是決定特定規則哪些層面的函式 。這是Rule ->Aspect 函式。

還有一點未預期的複雜問題:切面可附加至其他方面; 例如,收集 Java IDE 類別路徑的方面 想要知道 Classpath 的所有 .jar 檔案 通訊協定緩衝區在這種情況下,IDE 切面應該附加至 (proto_library 規則 + Java proto 切面)。

類別內擷取了切面的複雜度 AspectCollection

平台和工具鍊

Bazel 支援多平台建構作業,也就是說,可能在任一平台發布 用於執行建構動作和多個架構的 這是建構程式碼的依據這些架構在 Bazel 中稱為「平台」 parlance (完整說明文件 這裡)

平台會透過限制設定中的鍵/值對應來描述平台 (例如 限制值 (例如特定 CPU) 的概念和「限制值」的概念 例如 x86_64)。我們有「字典」 @platforms 存放區中的設定和值

「工具鍊」的概念源自於不同的平台 版本運作且鎖定的平台,可能需要使用 不同的編譯器舉例來說,特定 C++ 工具鍊可能會在 還能指定其他作業系統。Bazel 必須決定 C++ 基於既定的執行作業和目標平台使用的編譯器 (工具鍊說明文件 這裡)。

為此,工具鍊會加註一組執行作業和 支援的目標平台限制為此,請先定義 工具鍊分成兩個部分:

  1. toolchain() 規則,用於說明一組執行作業和目標 工具鍊支援並指出工具鍊支援哪些類型 (例如 C++ 或 Java) 它包含工具鍊 (後者以 toolchain_type() 規則表示)
  2. 適用於描述實際工具鍊的語言規則 (例如 cc_toolchain())

但由於我們必須知道 以便執行工具鍊解析作業和語言專屬的工具鍊 超過 *_toolchain() 項規則包含的資訊數量多出許多,因此規則涵蓋的內容更多 載入時間。

執行平台的指定方式如下:

  1. 在 WORKSPACE 檔案中,使用 register_execution_platforms() 函式
  2. 在指令列中使用 --extra_execution_platforms 指令列 選項

一組可用的執行平台會計算於 RegisteredExecutionPlatformsFunction

設定目標的目標平台取決於 PlatformOptions.computeTargetPlatform()這個平台列表 但最後也想支援多個目標平台 。

適用於所設定目標的工具鍊組合取決於 ToolchainResolutionFunction。這個函式的功能如下:

  • 一組已註冊的工具鍊 (位於 WORKSPACE 檔案與 設定)
  • 所需的執行與目標平台 (在設定中)
  • 所設定目標 ( UnloadedToolchainContextKey)
  • 已設定目標的執行平台限制 ( exec_compatible_with 屬性),以及相關設定 (--experimental_add_exec_constraints_to_targets),攝於 UnloadedToolchainContextKey

這會產生 UnloadedToolchainContext,基本上是來自 工具鍊類型 (以 ToolchainTypeInfo 執行個體表示) 與 選擇適當的工具鍊名稱為「卸載」原因是其中不包含 工具鍊本身,只有標籤

然後,使用 ResolvedToolchainContext.load() 實際載入工具鍊 並用於所設定目標的實作。

我們也有舊版系統,需要有一個「主機」 各個叢集代表的 設定旗標,例如 --cpu我們正在逐步轉換至以上版本 有些人會將 Cloud Storage 視為檔案系統 但實際上不是為了處理使用者仰賴舊版設定的情況 我們導入了 平台對應 ,在舊版旗標和新式平台限制之間進行轉譯 他們的代碼位於 PlatformMappingFunction,並使用非 Starlark 符號「小」 語言」。

限制

有時候,使用者可能想將指定目標設為只與少數幾個項目相容 平台。Bazel 認為有多項機制都能達到這個目標,但可惜的是:

  • 規則專屬限制
  • environment_group()/environment()
  • 平台限制

規則專屬的限制在 Google 中最常用於 Java 規則;他們 而且無法在 Bazel 中使用,但原始碼可能會 包含參照。用來管理這個選項的屬性稱為 constraints=

Environment_group() 和 environment()

這些規則是舊版機制,並未廣泛使用。

所有建構規則都可以宣告可以針對特定領域進行建構 「環境」是 environment() 規則的例項。

您可以為規則指定多種支援的環境:

  1. 透過 restricted_to= 屬性。這是最直接的 規格;容器可宣告規則支援的精確環境組合 這個群組
  2. 透過 compatible_with= 屬性。這樣可宣告環境規則 除了「標準」規格以外支援多種不同類型的環境 預設值。
  3. 透過套件層級屬性 default_restricted_to=default_compatible_with=
  4. 使用 environment_group() 規則中的預設規格。每次 環境屬於與主題相關的對等互連項目群組 (例如「CPU」 架構」、「JDK 版本」或「行動作業系統」)。 定義這類環境時 應為「default」的支援如果 restricted_to= / environment() 屬性。不含此類規則的規則 屬性會沿用所有預設值。
  5. 透過規則類別預設值。這會覆寫以下項目的全域預設值: 指定規則類別的例項。比方說 無須明確為每個執行個體明確地測試所有 *_test 規則 宣告此功能

environment() 是以一般規則的形式實作,environment_group() 則 是 Target 的子類別,但不是 Rule (EnvironmentGroup) 和 預設使用 Starlark 提供的函式 (StarlarkLibrary.environmentGroup()) 最終會建立匿名化的 目標。這是為了避免產生循環依附關係,因為每個 也需要宣告屬於該環境群組, 環境群組需要宣告其預設環境

建構作業可以限制在採用 --target_environment 指令列選項。

限制檢查的實作作業位於 《RuleContextConstraintSemantics》和《TopLevelConstraintSemantics》。

平台限制

目前的「官方」能夠說明指定目標與哪些平台相容 是使用和描述工具鍊和平台一樣的限制。 已在提取要求審核中 #10945

顯示設定

如果您參與的是 Google 等眾多開發人員的大型程式碼集, 我必須小心避免其他人 再也不是件繁重乏味的工作否則,根據Hyrum 的法律, 使用者仰賴您考慮到的導入行為 詳細資料。

Bazel 會透過名為「visibility」(可見性) 的機制支援此做法:您可以宣告 才能依賴 visibility 屬性。這個 屬性有點特殊功能 因為雖然其中包含標籤清單 標籤可能會將模式比套件名稱進行編碼,而不是指向任何 特定目標。(是的,這是設計瑕疵)。

我們會在下列位置實作這項資訊:

  • RuleVisibility 介面代表瀏覽權限宣告。這項服務可以 可以是固定 (完全公開或完全不公開) 或標籤清單。
  • 標籤可以參照套件群組 (預先定義的套件清單), 直接套件 (//pkg:__pkg__) 或套件的子樹狀結構 (//pkg:__subpackages__).這與指令列語法不同 其使用 //pkg:*//pkg/...
  • 套件群組已實作為本身的目標 (PackageGroup),並 設定的目標 (PackageGroupConfiguredTarget)。我們可以 如有需要,可以改用簡單的規則實作邏輯 藉助 PackageSpecification (對應 單一模式,例如 //pkg/...PackageGroupContents 對應 設為單一 package_grouppackages 屬性;和 PackageSpecificationProvider,匯總 package_group 和 及轉換的 includes
  • 從瀏覽權限標籤清單轉換為依附元件 DependencyResolver.visitTargetVisibility和另外幾個項目 例如減少干擾
  • 實際檢查 CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()

巢狀集合

設定的目標通常會從依附元件匯總一組檔案 新增自己的匯總資訊,並納入遞移資訊供應器中 並設定依附於該元件的目標則可執行相同操作。範例:

  • 用於建構的 C++ 標頭檔案
  • 代表 cc_library 遞移性關閉的物件檔案
  • 一組 .jar 檔案需要位於 Java 規則的類別路徑上 編譯或執行
  • Python 規則遞移性的一組 Python 檔案

若使用 ListSet 等方式直接做到這一點,最終我們的最終目標會是 二次記憶體用量:如果一個 N 規則鏈結,且每個規則都 檔案,我們就有 1+2 位以上的文件集成員。

為解決這個問題,我們提出了 NestedSet。而是由其他 NestedSet 組成的資料結構 和一些本身的成員,因此形成有向非循環圖 模型其所屬成員無法改變,成員可以反覆變更。我們會定義 多重疊代順序 (NestedSet.Order):preorder、 postorder、topological (節點一律位於其祖系之後) 和「不在乎,但應該是 。」

相同的資料結構在 Starlark 中稱為 depset

構件和動作

實際建構作業包含一組指令 使用者想要的輸出結果。這些指令是以 Action 類別和檔案會表示為該類別的執行個體 Artifact。它們以 兩個 聯盟、有向非循環圖排列,稱為 「動作圖表」

構件分為兩種:來源構件 (有可用構件) 和衍生構件 (需要在 Bazel 開始執行前) 和衍生構件 )。衍生成果本身可以有多種:

  1. **一般構件。**系統會透過計算 其核對和,以 mtime 作為快速鍵我們不會檢查檔案 ctime 沒有改變。
  2. 未解決的符號連結構件。系統會按照 呼叫 readlink() 為止與一般的構件不同 符號連結。通常用於在使用者將一些檔案封裝為單一容器時, 封存檔。
  3. 樹木構件。這些檔案並非單一檔案,而是目錄樹狀結構。他們 會檢查檔案中的一組檔案和 內容。以 TreeArtifact 表示。
  4. 常數中繼資料構件。變更這些構件不會觸發 重新建構此功能僅供用來建立印章資訊:我們不想要 因為目前時間有所變更

沒有根本原因,為何來源構件不得為樹狀結構,或 我們尚未實作未解析的符號連結構件 但在 BUILD 檔案中參照來源目錄會是其中一個 Bazel 有幾個已知的長期錯誤問題我們有 BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM 屬性)

有個明顯的Artifact是中間人。這些代碼由「Artifact」表示 例項就是 MiddlemanAction 的輸出內容這類模型的用途是 有幾件事要特別注意:

  • 透過匯總中間人,您可以將構件分組。如此一來 如果有很多操作都使用同一個大量輸入內容,我們就不會有 N*M 依附元件邊緣,只有 N+M (將替換為巢狀集合)
  • 排定依附元件中間門時間,確保動作會比其他動作先執行。 這些變數大多用於程式碼檢查,但也適用於 C++ 編譯 (詳情請參閱 CcCompilationContext.createMiddleman() 以取得說明)
  • 執行檔案中間門,確保執行檔案樹狀結構是否存在, 不需要個別依賴輸出資訊清單 執行檔案樹狀結構參照的單一構件

動作最適合理解為需要執行的指令 其需求和產生的輸出內容組合以下是 動作說明的組成要素:

  • 需要執行的指令列
  • 所需的輸入構件
  • 需要設定的環境變數
  • 註解,說明需要執行的環境 (例如平台) 。

此外還有一些特殊情況,例如編寫檔名 也不必擔心這些是 AbstractAction 的子類別。大多數動作都是 SpawnActionStarlarkAction (相同,但它們應該 不同的類別),但 Java 和 C++ 有專屬的動作類型 (JavaCompileActionCppCompileActionCppLinkAction)。

我們最終想將所有內容移至 SpawnActionJavaCompileAction 為 非常接近,但 C++ 屬於特殊情況,因為 .d 檔案會剖析 包括掃描

動作圖表大部分為「內嵌」轉換為 SkyFrame 圖表:從概念來看 動作的執行會在 ActionExecutionFunction。動作圖依附元件邊緣與 SkyFrame 依附元件邊緣的詳細說明 ActionExecutionFunction.getInputDeps()Artifact.key()有幾列 最佳化,以減少 SkyFrame 邊緣的數量:

  • 衍生的構件沒有自己的 SkyValueArtifact.getGeneratingActionKey() 是用來找出 即可
  • 巢狀組合都有專屬的 SkyFrame 鍵。

共用動作

部分動作是由多個已設定的目標產生。Starlark 規則 受到的限制,因為這類事件只能在 決定了目錄的硬體位置 (但即使如此 相同套件中的規則可能會發生衝突),但在 Java 中實作的規則可能會 無論在任何位置,都能產生完整的構件

這可能算是錯訊對象,但要消除這個行為實在很難 因為可以大幅節省執行時間 來源檔案必須以某種方式處理,並由 因此需要處理多項規則這是因為每個 RAM 都會耗用資源: 共用動作的執行個體必須個別儲存在記憶體中。

如果兩個動作產生的輸出檔案相同,則必須完全相同: 因此具備相同的輸入內容、相同的輸出內容並執行相同的指令列。這個 對等關係是在 Actions.canBeShared() 中實作, 並藉由檢視每一個「動作」,在分析和執行階段之間驗證。 這會在 SkyframeActionExecutor.findAndStoreArtifactConflicts() 中實作 是 Bazel 中需要「global」的幾個位置之一模型的 建構應用程式

執行階段

這類情況會在 Bazel 實際開始執行建構動作時,例如 會產生輸出內容

Bazel 在分析階段結束後,要做的第一件事是決定 您必須建構構件,此操作的邏輯會使用 TopLevelArtifactHelper;大約是 filesToBuild 設定目標,以及特殊輸出內容的內容 專門用於表示「如果這個目標是在命令上 建構這些構件

下一步是建立執行作業根目錄。由於 Bazel 可以選擇讀取 從檔案系統中不同位置的來源套件 (--package_path), 則需要用完整原始碼樹狀結構提供本機執行的動作。這是 由 SymlinkForest 類別處理,且會記錄每個目標 ,並建構將符號連結的單樹狀目錄 每個包含使用目標的實際位置的套件。另一種替代方法是 傳遞正確的路徑給指令 (將 --package_path 納入考量)。 這不適合您,原因如下:

  • 從套件路徑移動套件時,這個目錄會變更動作指令列 項目之間 (這是很常見的情況)
  • 如果是在遠端執行動作,則會產生不同的指令列 是本機執行
  • 需要根據使用中的工具進行專屬的指令列轉換 (請考量 Java 類別路徑和 C++ include 路徑之間的差異)
  • 變更動作的指令列後,動作快取項目會失效
  • --package_path 目前緩慢地淘汰

然後 Bazel 開始掃遍動作圖 (雙部分有向性圖表) 由動作、其輸入和輸出構件) 和執行中的動作組成。 每個動作的執行都會以 SkyValue 的執行個體表示 ActionExecutionValue 類別。

由於執行動作的費用較高,因此我們有幾層快取, 透過 SkyFrame:

  • ActionExecutionFunction.stateMap 包含會重新啟動 SkyFrame 的資料 的 ActionExecutionFunction 折扣
  • 本機動作快取包含檔案系統狀態的相關資料
  • 遠端執行系統通常也包含自己的快取

本機動作快取

這個快取是位於 SkyFrame 後的另一層。即使動作 在 SkyFrame 中重新執行,但在本機動作快取中仍可能發生命中。這項服務 代表本機檔案系統的狀態,且系統會序列化到磁碟 換句話說,當伺服器啟動新的 Bazel 伺服器時,可能會取得本機動作快取 就算 SkyFrame 圖表空白

系統會透過 ActionCacheChecker.getTokenIfNeedToExecute()

有別於名稱,這是從衍生產物的路徑到 發出指令的動作操作說明如下:

  1. 其輸入和輸出檔案集,以及其總和檢查碼
  2. 它是「動作鍵」,通常會是執行的指令列 一般而言,代表 輸入檔案 (例如 FileWriteAction 的情況下,這就是資料總和檢查碼) 您寫了什麼)

還有一個高度實驗性的「由上而下動作快取」 會使用遞移雜湊值,避免將資料儲存在快取中 次。

輸入探索和輸入內容縮減

某些動作比只輸入一組輸入內容來得複雜。變更內容 動作輸入項目會以兩種形式呈現:

  • 動作可能會在執行之前發現新的輸入內容,或決定 並非必要。標準化範例為 C++ 此時較有利於猜測哪些標頭檔案是 C++ 檔案使用了遞移性關閉,因此我們不考慮將 檔案複製到遠端執行程式因此,我們可以選擇不要 標頭檔案做為「輸入內容」,但掃描來源檔案即可 並且只將這些標頭檔案標示為 #include 陳述式中提及 (我們為高估價, 實作完整的 C 預先處理器) 此選項目前是以有線方式 「false」且僅供 Google 使用
  • 動作可能會發現執行過程中並未有人用到某些檔案。於 這稱為「.d 檔案」:編譯器會告訴 再次使用,但為了避免痛苦 而 Bazel 也會使用這一點這麼做 而非 include 掃描器,因為這個程式庫仰賴編譯器。

這些函式是透過「動作」上的方法來實作:

  1. 系統會呼叫 Action.discoverInputs()。系統應會傳回一組巢狀結構的 經判定為必要成果的構件。這些必須是來源構件 ,這樣動作圖表中沒有沒有依附元件的邊緣 設定目標圖表中的對等項目
  2. 透過呼叫 Action.execute() 即可執行此動作。
  3. Action.execute() 結束時,動作可以呼叫 Action.updateInputs(),用於告知 Bazel 並未將所有輸入內容 。如果使用的輸入內容 回報為未使用

動作快取在新的「動作」執行個體 (例如建立的內容) 上傳回命中時 伺服器重新啟動後,Bazel 會自行呼叫 updateInputs(),讓系統 輸入內容會反映先前輸入探索和修剪的結果。

Starlark 動作可以使用設施,將某些輸入內容宣告為未使用 使用應用程式的 unused_inputs_list= 引數 ctx.actions.run()

各種動作執行方式:策略/ActionContext

某些動作能以不同方式執行。舉例來說,指令列可以是 在本機上執行,但會有多種沙箱或遠端執行。 體現它的概念稱為 ActionContext (或 Strategy),因為我們 成功完成一半的重新命名作業...)

動作情境的生命週期如下:

  1. 執行階段啟動時,BlazeModule 執行個體會詢問什麼內容 以及他們具備的動作情境這項作業會在 ExecutionTool。動作結構定義類型以 Java Class 識別 執行個體是指 ActionContext 的子介面 也就是動作內容必須實作的介面
  2. 從可用的動作中選取所需動作內容, 已轉寄至 ActionExecutionContextBlazeExecutor
  3. 動作要求背景資訊,使用 ActionExecutionContext.getContext()BlazeExecutor.getStrategy() (設計上應該只有一個方法 它...)

策略可以自由呼叫其他策略來完成工作。用途包括 以在本機和遠端啟動動作的動態策略中 然後採用先完成者

值得一提的策略是實作永久工作站程序 (WorkerSpawnStrategy).概念是有些工具的啟動時間很長 因此,您應該在不同動作之間重複使用,而不必從頭開始 每項動作都代表一個潛在的正確問題,因為 Bazel 依賴 worker 程序的承諾,也就是在處理程序中 個別要求之間的狀態)

如果工具發生變更,則必須重新啟動工作站程序。是否為工作站 但該模型可對所用工具計算總和檢查碼 WorkerFilesHash。關鍵在於瞭解哪些動作的輸入內容 代表輸入的內容是由創作者決定 設定為 Spawn.getToolFiles(),而 Spawn 的執行檔案是 仍視為工具的一部分

進一步瞭解策略 (或行動情境!):

  • 以下說明各種執行動作的策略相關資訊 請按這裡
  • 與動態策略相關的資訊,我們會同時執行動態策略 在本機和遠端中查看最先完成的任務 請按這裡
  • 提供在本機執行動作的複雜資訊 請按這裡

本機資源管理員

Bazel 可以平行執行許多動作。有多少本地動作 減少同時運作的執行個體數量,以避免 超載本機機器

這會在 ResourceManager 類別中實作:每個動作都必須 附帶估算當地資源用量、 ResourceSet 執行個體 (CPU 和 RAM)。當動作情境有所改變時 需要本機資源時才會呼叫 ResourceManager.acquireResources() 都會遭到封鎖,直到取得必要資源為止

如需本機資源管理的詳細說明,請參閱 請按這裡

輸出目錄的結構

每項動作都必須在輸出目錄中指定獨立位置 輸出內容衍生構件的位置通常如下所示:

$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>

與特定項目相關聯的目錄名稱 該怎麼辦?有兩個衝突的理想屬性:

  1. 如果同一個建構中可以發生兩項設定,兩組設定必須 兩個不同的目錄,因此兩個版本可以擁有 動作;否則,如果兩項設定有異議,例如指令 執行相同輸出檔案的動作行,Bazel 不知道 選擇動作 (「動作衝突」)
  2. 如果兩項設定代表「粗略估計」同理,他們應該 相同名稱,這樣在一個容器中執行的動作,就能重複用於 指令列選項:例如,將指令列選項變更為 Java 編譯器不應導致 C++ 編譯動作重新執行。

到目前為止,我們還沒有找到解決這個問題的原則 與設定修剪相同的問題較長的討論 可用的選擇 請按這裡。 主要有問題的領域是 Starlark 規則 (作者通常不會 非常熟悉 Bazel) 和幾項功能 可以產生「相同」的輸出檔案

目前的做法是設定的路徑區隔 已新增多個後置字串的 <CPU>-<compilation mode>,以便進行設定 在 Java 中實作的轉換不會導致動作衝突。此外, 新增 Starlark 設定轉換組合的總和檢查碼,讓使用者 就無法引發動作衝突距離完美遙遠。這會在 OutputDirectories.buildMnemonic(),需要每個設定片段 將單獨的部分新增至輸出目錄的名稱中。

測試命名空間

Bazel 對執行測試提供了豐富支援。支援以下項目:

  • 從遠端執行測試 (如有遠端執行後端)
  • 同時多次執行測試 (用於分解或收集時間) 資料)
  • 資料分割測試 (針對多個程序在相同的測試中分割測試案例) (速度)
  • 重新執行不穩定的測試
  • 將測試分組成測試套件

測試是一般設定的目標,具有 TestProvider, 如何執行測試:

  • 建構結果執行測試的構件。這就是「快取」 狀態」包含序列化 TestResultData 訊息的檔案
  • 應執行測試的次數
  • 應拆分測試的資料分割數量
  • 關於測試執行方式的一些參數 (例如測試逾時)

決定要執行的測試

決定要執行哪些測試是相當繁複的程序。

首先,在目標模式剖析期間,測試套件會以遞迴方式展開。 已在 TestsForTargetPatternFunction 中實作。還算關心 令人驚訝的是,如果測試套件未宣告任何測試,就表示 每次測試。這會在 Package.beforeBuild() 中實作,方法為 新增名為 $implicit_tests 的隱含屬性,以測試套件規則。

然後根據 指令列選項這會在 TestFilter 中實作,並從 在目標剖析期間,以及 TargetPatternPhaseFunction.determineTests() 結果會放入 TargetPatternPhaseValue.getTestsToRunLabels() 中原因 為什麼無法設定可篩選的規則屬性 因此設定並非 廣告。

然後,系統會在 BuildView.createResult() 中進一步處理: 分析失敗時,系統會將這些測試拆分為獨有的 非專屬測試它會放入 AnalysisResult 中,如下所示 ExecutionTool 知道要執行哪些測試。

為了讓這項流程更公開透明,tests() 查詢運算子 (在 TestsFunction 中實作) 可判斷哪些測試 會在指令列中指定特定目標時執行。是 很可惜的是,改頭換面,所以可能與上述資料有出入。 提供多種巧妙的方式

執行測試

執行測試的方式就是要求快取狀態構件。然後 就會執行 TestRunnerAction,最終會呼叫 TestActionContext使用的 --test_strategy 指令列選項 會以要求的方式執行測試。

根據使用環境變數的詳細通訊協定執行測試 以便進行測試Bazel 的詳細說明 可預期的測試,以及 Bazel 可預期的測試內容 請按這裡。在 簡單來說,結束代碼 0 表示成功,其他任何項目則代表失敗。

除了快取狀態檔案外,每個測試程序都會發出 檔案。這些會放在「測試記錄檔目錄」中也就是名為 目標設定的輸出目錄的 testlogs

  • test.xml,這是 JUnit 樣式的 XML 檔案,詳細說明瞭 測試資料分割
  • test.log,測試的主控台輸出內容。stdout 和 stderr 都不是 分隔。
  • test.outputs,「未宣告的輸出目錄」;以便測試 使用 Cloud SQL 時 要輸出檔案和在終端機輸出的檔案

測試執行期間可能會發生兩種情況 建構一般目標:專屬的測試執行與輸出內容串流。

部分測試必須在專屬模式下執行,例如不使用 就好。方法是將 tags=["exclusive"] 加入 測試規則或透過 --test_strategy=exclusive 執行測試。每項專屬 測試是由個別的 SkyFrame 叫用執行,要求執行 「main」的建構應用程式這會在 SkyframeExecutor.runExclusiveTest()

與一般動作不同,動作會在動作發生時傾印終端機輸出內容 完成後,使用者可以要求串流測試的輸出內容,以便他們進行串流。 會收到長時間執行測試的進度通知它是由 --test_output=streamed 指令列選項,且隱含專屬測試 避免交錯不同測試的輸出內容。

這會在適當命名的 StreamedTestOutput 類別中實作,並將 輪詢相關測試的 test.log 檔案變更,並傾印 傳送到 Bazel 規則的終端機

系統會觀察事件匯流排,提供所執行測試的結果 各種事件 (例如 TestAttemptTestResultTestingCompleteEvent)。 系統會將這些金鑰轉儲到建構事件通訊協定,並傳送到控制台 上傳者:AggregatingTestListener

涵蓋範圍集合

系統會根據檔案中的 LCOV 格式來回報涵蓋率 bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

為了收集涵蓋率,每次測試執行作業都會包裝在一個名為 的指令碼中 collect_coverage.sh

這個指令碼會設定測試環境,啟用涵蓋範圍收集功能 並判斷涵蓋率檔案寫入的位置。 然後執行測試。測試本身可以執行多個子程序, 分別以多種不同程式語言撰寫而成 ( 涵蓋率收集執行階段)。包裝函式指令碼負責 產生的檔案為 LCOV 格式,並合併為單一 檔案。

collect_coverage.sh 的交互作用是由測試策略和 測試的輸入內容中需要 collect_coverage.sh。這是 透過隱含屬性 :coverage_support 完成,而該屬性會解析為 設定旗標 --coverage_support 的值 (請參閱 TestConfiguration.TestOptions.coverageSupport)

有些語言會離線檢測,也就是說 檢測是在編譯期間新增 (例如 C++),其他檢測則是在線上進行 檢測,意味著系統會在執行時新增涵蓋率檢測 讓應用程式從可以最快做出回應的位置 回應使用者要求

另一個核心概念是「基準涵蓋範圍」。這是圖書館的報導 或是測試任何程式碼是否已執行。但問題在於解決 假如您想計算二進位檔的測試涵蓋範圍,合併 涵蓋率,因為二進位檔中的程式碼可能 或連結至任何測試因此,我們要針對 二進位檔案,只包含我們蒐集的涵蓋率,而未採用任何保固方案的檔案 即可指定目標的基準涵蓋範圍檔案為 bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat模型 ,並用於測試,並用於 kube-APIserver --nobuild_tests_only 標記傳送至 Bazel。

基準涵蓋率目前中斷。

我們會針對每項規則追蹤涵蓋的兩組檔案: 檢測檔案和一組檢測中繼資料檔案。

那組檢測檔案就是,用於檢測的一組檔案。適用對象 線上涵蓋率執行階段中,這可在執行階段使用,以決定要在 樂器。這個巨集也會用於導入基準涵蓋範圍。

檢測中繼資料檔案集是測試所需的額外檔案組合 才會產生 Bazel 所需的 LCOV 檔案實務上,這包括 執行階段特定檔案;例如 gcc 會在編譯期間發出 .gcno 檔案 如果涵蓋範圍模式為

是否收集涵蓋率資訊會儲存在 BuildConfiguration。這項功能很實用,因為您可以輕鬆變更測試 視這個位元而定,這也表示 就可以重新分析所有目標 (某些語言,例如 C++ 需要不同的編譯器選項來發出可收集涵蓋率的程式碼, 能緩解這個問題,因為必要時仍需要重新分析)。

涵蓋範圍支援檔案須透過標籤隱含 讓叫用政策可覆寫這些依附元件,進而允許 用於不同版本的 Bazel理想情況下 然後將差異移除,然後為其中一個標準進行標準化。

我們也會產生一份「涵蓋率報告」其中含有 執行 Bazel 叫用的每個測試這個程序是由 CoverageReportActionFactory,並且是從 BuildView.createResult() 呼叫。這項服務 藉由查看 :coverage_report_generator,取得所需的工具 屬性。

查詢引擎

Bazel 有一個 小聲的用語 這個模型用來詢問各種圖表的各種資訊下列查詢種類 :

  • bazel query 可用來調查目標圖表
  • bazel cquery 可用來調查設定的目標圖表
  • bazel aquery 可用來調查動作圖表

每個項目都可以藉由建立 AbstractBlazeQueryEnvironment 子類別來實作。 如要將其他查詢函式設為子類別,則可將 QueryFunction 設為子類別 ,直接在 Google Cloud 控制台實際操作。以便串流查詢結果,而不是收集到 資料結構,query2.engine.Callback 會傳遞至 QueryFunction 才能傳回結果

查詢結果可透過多種方式產生:標籤、標籤和規則 類別、XML、protobuf 等這些會實作為 OutputFormatter

對某些查詢輸出格式 (原型) 有一項細微要求: Bazel 需要發出「所有」_封裝載入套件提供的資訊, 可以比較輸出內容,並判斷特定目標是否已變更。 因此,屬性值必須可序列化,這就是為什麼 只有少數屬性類型,沒有複雜的 Starlark 屬性 輕鬆分配獎金常見的解決方法是使用標籤,將複雜的 套用該標籤的規則這並不是個好辦法 如果能解除這項規定,會很有幫助

模組系統

只要在 Bazel 中新增模組,即可擴充 Bazel。每個模組都必須具備子類別 BlazeModule (這個名稱是 Bazel 歷史記錄的重複版本, 稱為 Blaze),並在執行 指令

最常用於實作「非核心」的不同部分功能 只有部分 Bazel 版本 (例如 Google 使用的版本) 需要:

  • 遠端執行系統的介面
  • 新指令

BlazeModule 提供的延長點數組合可能有些危險。錯誤做法 我們會透過該範本 做為良好的設計原則

活動匯流排

BlazeModule 與 Bazel 的其餘部分通訊的主要方式是事件匯流排 (EventBus):系統會為每個建構作業 ( Bazel 的各個部分) 建立新的執行個體 可以在模組中張貼事件,而模組可針對其所在的事件註冊事件監聽器 舉例來說,下列項目會以事件表示:

  • 確定建構目標清單已確定 (TargetParsingCompleteEvent)。
  • 已確定頂層設定 (BuildConfigurationEvent)。
  • 目標已建構,但成功或未建立 (TargetCompleteEvent)
  • 已執行測試 (TestAttemptTestSummary)

其中部分事件會在 Bazel 之外,出現在 建立事件協定 (這些值為 BuildEvent)。這不僅允許 BlazeModule 也能使用 ,以觀察建構。可供使用者存取 任何內含通訊協定訊息的檔案,或者 Bazel 都可以連線至伺服器 (稱為 「建構事件服務」) 來串流事件。

這會在 build.lib.buildeventservice 中實作, build.lib.buildeventstream Java 套件。

外部存放區

而 Bazel 原本就是設計用於單聲道存放區 (單一原始碼) 而 Bazel 就是要建構它所需要的一切; 但不一定是如此「外部存放區」是一種抽象化機制 連接這兩個世界:它們代表建構所需的程式碼,但 而非主原始碼樹狀結構中

WORKSPACE 檔案

外部存放區組合是由剖析 WORKSPACE 檔案決定。 例如,如下所示的宣告:

    local_repository(name="foo", path="/foo/bar")

存放區中名為 @foo 的結果可供使用。適用位置 複雜在於可以在 Starlark 檔案中定義新的存放區規則 即可用來載入新的 Starlark 程式碼,即可用來 存放區規則等...

為了處理這種情況,請剖析 WORKSPACE 檔案 (在 WorkspaceFileFunction) 分成多個區塊,分別以 load() 分隔 聲明。區塊索引會以 WorkspaceFileKey.getIndex() 表示 計算WorkspaceFileFunction直到索引 X 才會代表評估到 Xth load() 陳述式。

正在擷取存放區

將存放區的程式碼提供給 Bazel 之前,必須先完成 已擷取。這會導致 Bazel 在以下位置建立目錄: $OUTPUT_BASE/external/<repository name>

擷取存放區的步驟如下:

  1. PackageLookupFunction 瞭解需要存放區並建立 RepositoryNameSkyKey,會叫用 RepositoryLoaderFunction
  2. RepositoryLoaderFunction 將要求轉寄給 RepositoryDelegatorFunction 因不明原因 (代碼表示使用者: 避免在 SkyFrame 重新啟動時重新下載項目,但這並非 非常穩固的推理)
  3. RepositoryDelegatorFunction 並找出其要求指定的存放區規則 疊代擷取 WORKSPACE 檔案的區塊,直到要求 找到存放區
  4. 找到實作了存放區的適當 RepositoryFunction fetching;它可以是存放區的 Starlark 實作 硬式編碼的硬式編碼地圖。

由於擷取存放區可能非常耗時 高價位:

  1. 下載的檔案會有快取,透過總和檢查碼進行索引鍵 (RepositoryCache).這需要檢查碼的 WORKSPACE 檔案,但這對於文化的特性也很好。共用者: 相同工作站中的每個 Bazel 伺服器執行個體 執行 Pod 的工作區或輸出內容基礎
  2. 「標記檔案」都會寫入 $OUTPUT_BASE/external 底下的各個存放區 ,其中包含用於擷取規則的總和檢查碼。如果 Bazel 伺服器重新啟動,但核對和不會變更,因此不會重新擷取。這個 是在 RepositoryDelegatorFunction.DigestWriter 中實作。
  3. --distdir 指令列選項會指定另一個用來 查詢要下載的構件這在企業設定中非常實用 其中 Bazel 不應從網際網路擷取隨機內容。這是 由 DownloadManager 執行。

下載存放區後,系統會將存放區中的構件視為來源 導致學習失真性這會造成問題,因為 Bazel 通常會檢查最新版本 對來源構件呼叫 stat(),而且這些構件 如果存放區的定義有所變更,就會失效。因此, 外部存放區中構件的 FileStateValue 必須依附 外部存放區這個程序由「ExternalFilesHelper」處理。

代管目錄

有時外部存放區需要修改工作區根目錄下的檔案 (例如,套件管理工具會將所下載套件的子目錄放在 來源樹狀結構)。這與假設 Bazel 會使該來源發生問題 檔案,僅供使用者修改,而非自己修改,且允許套件 請參閱工作區根目錄下的每個目錄為了將這類網路 Bazel 會執行以下兩項作業:

  1. 允許使用者指定工作區 Bazel 的子目錄 才能傳入的 Pod這些指令會列在名為 .bazelignore 的檔案中 功能是在 BlacklistedPackagePrefixesFunction 中實作
  2. 我們會將工作區子目錄的對應關係編碼到外部 並交由 ManagedDirectoriesKnowledge 處理 FileStateValue 指稱它們的方式與一般 使用外部存放區

存放區對應

可能會有多個存放區想依附同一個存放區 但在不同版本中 (這是「鑽石依附元件」的執行個體) 問題」)。例如,如果在建構作業中的不同存放區中有兩個二進位檔 想要依賴 Guava 時,他們會同時提及有標籤的 Guava 我們將從 @guava//開始推出,並預期這意味著不同的版本。

因此,Bazel 允許您重新對應外部存放區標籤, 字串 @guava// 可參照一個 Guava 存放區 (例如 @guava1//) 提供一個二進位檔和另一個 Guava 存放區 (例如 @guava2//) 另一個存放區

此外,這也可用來「加入」鑽石。如果存放區是 另一個依附於 @guava1//,另一個則取決於 @guava2//、存放區對應 如此一來,其中一個存放區就能重新對應兩個存放區,使用標準 @guava// 存放區。

在 WORKSPACE 檔案中指定對應為 repo_mapping 屬性 個別存放區定義的架構隨後在 SkyFrame 中會顯示為 WorkspaceFileValue,其主要用於:

  • Package.Builder.repositoryMapping,用來轉換標籤值 將 Deployment 中規則的屬性 RuleClass.populateRuleAttributeValues()
  • Package.repositoryMapping,用於分析階段 (用於 解析 $(location) 等載入中未剖析的內容 階段)
  • BzlLoadFunction:用於解析 load() 陳述式中的標籤

JNI 位元

Bazel 的伺服器主要是_在 Java 中「寫入」。不過 Java 無法在實作時單獨執行,或無法單獨執行。這個 大多只能與檔案系統的互動、程序控制及 有多種不同的低層級項目

C++ 程式碼位於 src/main/native 和包含原生的 Java 類別中 方法:

  • NativePosixFilesNativePosixFileSystem
  • ProcessUtils
  • WindowsFileOperationsWindowsFileProcesses
  • com.google.devtools.build.lib.platform

控制台輸出

發出主控台輸出內容似乎很簡單 多個處理程序 (有時從遠端為遠端程序)、精細快取 內含豐富繽紛的終端機輸出,而有長時間執行的伺服器後, 有點複雜

在遠端程序呼叫 (RPC) 呼叫來自用戶端後,兩個 RpcOutputStream 例項建立 (適用於 stdout 和 stderr),將輸出的資料轉送至該執行個體 然後再提供給客戶然後納入 OutErr (stdout、stderr) 配對)。任何需要列印在主控台上的資訊都會經過這些 串流。接著這些訊息串 BlazeCommandDispatcher.execExclusively()

根據預設,輸出內容會以 ANSI 逸出序列顯示。如果這些 所需值 (--color=no) 則會由 AnsiStrippingOutputStream 去除。於 以及 System.outSystem.err 會重新導向至這些輸出串流。 如此一來,即可使用 System.err.println(),且最後仍會在用戶端的終端機輸出內容中 (與伺服器的設定不同)。如果處理過程 會產生二進位檔輸出內容 (例如 bazel query --output=proto),不會包括 stdout 。

簡短訊息 (錯誤、警告等) 會透過 EventHandler 介面。值得一提的是 EventBus (令人困惑)。每個 Event 都有一個 EventKind (錯誤 警告、資訊等一些內容),而且可能有 Location (在 產生事件的原始碼)。

部分 EventHandler 實作會儲存收到的事件。這會使用 各種類型的快取處理行為造成的 UI 重播資訊 例如,快取設定的目標所發出的警告。

部分 EventHandler 也允許張貼活動, 事件匯流排 (一般 Event 「不會」__出現在其中)。這些 ExtendedEventHandler 的實作及其主要用途是重播快取 EventBus 事件。這些 EventBus 事件全都實作 Postable,但並非 發布至 EventBus 的所有內容都會導入這個介面; 只有由 ExtendedEventHandler 快取的快取會比較好 (這樣會很不錯 大部分的事情都會做得到而不會強制執行)

終端機輸出內容大多會透過 UiEventHandler 發出, 負責所有高品質的輸出格式和進度回報 Bazel 確實如此其中包含兩種輸入內容:

  • 活動匯流排
  • 透過回報器將事件串流填入其中

指令執行機器的唯一直接連線 (例如 Bazel) 必須經由 Reporter.getOutErr() 處理遠端程序呼叫 (RPC) 串流至用戶端, 可讓你直接存取這些訊息串只有在指令需要時才會使用 傾印大量可能的二進位資料 (例如 bazel query)。

剖析 Bazel

Bazel 的運作速度很快。Bazel 也同樣緩慢,因為建構作業持續成長至 可說是重點因此,Bazel 納入的分析器可 這個檔案用於剖析建構作業和 Bazel 本身會在 名為 Profiler 的精確名稱。這項功能預設為開啟,但只會記錄錄製過程 簡化資料,以便分配負擔指令列 --record_full_profiler_data 能錄下所有內容。

該設定檔會產生採用 Chrome 分析器格式的設定檔。使用 Chrome 瀏覽效果最佳 其資料模型是工作堆疊:一個工作可以開始工作,以及結束工作 這些物件應該整齊地嵌入在一起每個 Java 執行緒都會取得 各自的工作堆疊待辦事項:運作方式與動作和 延續路徑?

分析器已在 BlazeRuntime.initProfiler() 中啟動及停止。 分別是 BlazeRuntime.afterCommand(),並且嘗試保持上線時間太長 這樣我們就能剖析所有內容若要在商家檔案中新增項目: 呼叫 Profiler.instance().profile()。其會傳回 Closeable,其閉包 代表工作結束最適合用於試用資源 聲明。

我們也會在 MemoryProfiler 中執行基本記憶體剖析。一律開啟這項設定 而且大多可記錄最大堆積大小和 GC 行為

測試 Bazel

Bazel 有兩種主要的測試:將 Bazel 視為「黑箱」的測試和 只會執行分析階段的工作我們將先前的「整合測試」稱為「整合測試」 與後者的「單元測試」比較像是整合測試 但整合性較差我們還有一些實際單元測試 無從得知

整合測試有兩種類型:

  1. 要導入這個架構,導入 src/test/shell
  2. 以 Java 實作。這些會實作為 BuildIntegrationTestCase

BuildIntegrationTestCase 是建議使用的整合測試架構 可以用於大多數測試情境由於這是 Java 架構 提供偵錯功能,並與許多常見的開發作業完美整合 工具。BuildIntegrationTestCase類別 Bazel 存放區

分析測試會以 BuildViewTestCase 的子類別實作。另有 您可以使用暫存檔案系統來編寫 BUILD 檔案,然後運用各種輔助程式 方法,可以要求設定的目標、變更設定及斷言 分析結果的各種事項