Bazel 建構作業輸出內容

Bazel 使用非常一致的配置來儲存建構構件,是開發人員常會感到混淆的管道。本頁會嘗試釐清運作方式。

Bazel output_base

根據設計,bazel build 指令一律不會將檔案寫入專案的來源目錄 (或其子目錄)。Bazel 改為使用使用者專屬的 平行目錄來儲存所有輸出內容 (稱為 user_output_root),如下所示:

  • Linux 系統中的 ~/.cache/bazel/_bazel_$USER
  • /private/var/tmp/_bazel_$USER (macOS)。
  • %HOME%\_bazel_%USERNAME% (Windows)。

針對執行 bazel 的每個工作區目錄,在 user_output_root 下會建立名為 output_base 的獨立目錄,如下所示:

  ${user_output_root}/<WORKSPACE_HASH>`

其中 <WORKSPACE_HASH> 是長十六進位雜湊 (根據目錄的絕對路徑計算)。

由於這個路徑完全無法預測,因此在 Bazel 專案中使用時,bazel info output_base 指令會列印該路徑。例如:

$ mkdir -p /tmp/project1 && cd /tmp/project1 && touch WORKSPACE.bazel
$ bazel info output_base
Starting local Bazel server and connecting to it...
/usr/local/google/home/digit/.cache/bazel/_bazel_digit/6c7b78994da78136b5cb6b7607361ad3

$ mkdir -p /tmp/project2 && cd /tmp/project2 && touch WORKSPACE.bazel
$ bazel info output_base
Starting local Bazel server and connecting to it...
/usr/local/google/home/digit/.cache/bazel/_bazel_digit/c37b9d68308ee5abe2f781dd38b733b9

$ mkdir -p /tmp/not-a-project && cd /tmp/not-a-project
$ bazel info output_base
WARNING: Invoking Bazel in batch mode since it is not invoked from within a workspace (below a directory having a WORKSPACE file).
ERROR: The 'info' command is only supported from within a workspace (below a directory having a WORKSPACE file).
See documentation at https://bazel.build/concepts/build-ref#workspace

這個配置很有彈性,但並不完美:

  • Pro:同一部機器的多位使用者可共用同一個唯讀專案目錄。

  • Pro:同一位使用者的多個專案目錄一律會使用獨立的輸出路徑。

  • 缺點:想直接從指令列或圖形探索工具查看產生的檔案非常困難。

  • 缺點:移除專案目錄 (例如使用 rm -rf .../my-project) 並不會移除其輸出內容 (這是重要的廢棄物來源)。

  • 缺點:移動專案目錄 (例如使用 mv my-project my-project2) 時,不會重複使用先前的 output_base 內容,而舊有的 output_base 內容現已無法存取。

  • 缺點user_output_root 的預設位置,因此 output_base 通常不位於專案的相同檔案系統 / 分區。這可能會對效能 / 磁碟用量造成非預期的結果。

呼叫 bazel clean 即可從目前的 output_base 移除建構輸出內容。這項作業必須「先」完成,再移除來源專案目錄。

在實務上,您可以輕鬆利用過時的 Bazel 專案 (從未經過妥善清理) 清除的建構構件,破壞 user_output_root 的內容。更糟的是,如果嘗試直接移除 user_output_root,可能就無法成功,因為根據預設,Bazel 會建立唯讀建構構件,這會導致 rm -rf ~/.cache/bazel 等指令無法運作!

Bazel output_base 內容:

其實有數個項目都儲存在 output_base 底下:

  • 外部存放區的 Workspace 目錄

    這些依附元件會對應到外部專案依附元件。通常這些都不是專案原始碼樹狀結構的一部分,而是從網路下載或以程式產生的。

    其內容會儲存在 ${output_base}/external/<repository_name> 底下,其中 external 部分是硬式編碼,<repository_name> 則與外部存放區的標準名稱相符。

  • 建構構件

    執行 bazel build 產生的檔案。儲存位置如下:

    ${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/bin/
    

    在此情況下:

    • execrootbazel-outbin 部分是硬式編碼,無法變更。

    • 針對專案自己的 BUILD.bazel 檔案中定義的目標,<workspace_name> 會預設為 __main__,除非您在專案的 WORKSPACE.bazel 檔案中設定具有類似指令的指令:

      workspace(
        name = "my_project",
      )
      ```
    
    - For targets defined in external repositories, `<workspace_name>` matches
    the repository's canonical name.
    
    - The `<config_dir>` value is a name derived from the build configuration used
    to configure the target that generated the build artifact. This allows
    rebuilding the same target in different ways, each time using a different
    `<config_dir>` value.
    
    Note: The `<config_dir>` value is **generally unpredictable**. More on this [here][bazel-config-dirs]
    
  • 測試結果

    呼叫 bazel test 時產生的記錄檔,儲存在 ${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/testlogs/ 下。

  • 內部快取和設定檔

    供遠端建構作業和遠端快取功能使用。開發人員可忽略這些檔案。

Bazel execroot 目錄:

execroot 會用於執行產生建構構件的 Bazel 指令,但做法取決於特定動作是否啟用沙箱機制。

  • 在 Linux 和 MacOS 中,所有動作皆預設啟用沙箱功能。 從 Bazel 7 開始,Windows 沒有沙箱支援。

  • Bazel 動作可以在定義中使用 no-sandbox 標記,刻意停用沙箱功能。

  • 叫用 bazel 時,您可以使用 --spawn_strategy=local 等選項在全域停用沙箱功能。

未使用沙箱:

停用沙箱功能後,凡是為特定工作區產生構件的建構動作,都會將輸出檔案放在 ${output_base}/execroot/<workspace_name> 下。

動作指令中顯示的來源檔案和建構構件的所有路徑都會因此成為相對路徑。

Bazel 確保在啟動指令之前,系統會在 execroot 下建立指令使用的輸入來源的符號連結。

舉例來說,會編譯 //src/foo/foo.cc 檔案 (包含 #include "foo.h") 且對應至 //src/foo/foo.h 的動作看起來可能會像這樣:

gcc -c -o bazel-out/k8-fastbuild/bin/src/foo/foo.o src/foo/foo.cc -Isrc/foo

有效原因:

  • 執行指令之前,Bazel 會建立指向 $PROJECT/src 的符號連結 ${output_base}/execroot/__main__/src,以便 src/foo/foo.ccsrc/foo/foo.h 按照預期方式解析 $PROJECT/src/foo/foo.cc$PROJECT/src/foo/foo.h

  • bazel-out/k8-fastbuild/bin/src/foo/foo.o 位置是最終輸出路徑,對於透過這個指令使用建構設定編譯 foo.cc 建立的物件檔案而言。

使用沙箱功能:

啟用沙箱機制後,Bazel 會為每個指令建立暫時目錄 (例如 ${output_base}/sandbox/linux-sandbox/<random-number>),並建立符號連結樹狀結構,藉此模擬其下的執行根版面配置,但僅適用於已知的輸入內容。在本例中,看起來會像這樣:

  • 輸入內容 ${sandbox}/execroot/__main__/src/foo/foo.cc${sandbox}/execroot/__main__/src/foo/foo.h 的符號連結分別指向 $PROJECT/src/foo/foo.cc$PROJECT/src/foo/foo.h

  • ${sandbox}/execroot/__main__ 下方執行完全相同的指令。而不是 ${output_base}/execroot/__main__

  • 指令完成後,將已知輸出內容從 ${sandbox}/execroot/__main__/bazel-out/k8-fastbuild/bin/src/foo/foo.o 的沙箱路徑複製到其 ${output_base}/execroot/__main__/bazel-out/k8-fastbuild/bin/src/foo/foo.o 的最終位置。

  • 最後,沙箱目錄及其所有內容都會遭到移除。這也表示系統會忽略未宣告的輸出內容。