規則

回報問題 查看來源 夜間 7.2 7.1 7.0 6.5 6.4

規則可定義 Bazel 執行的一系列動作 會產生一組輸出內容,在 規則傳回的 providers 實作函式。舉例來說 二進位規則可能會:

  1. 採用一組 .cpp 來源檔案 (輸入)。
  2. 在來源檔案 (動作) 上執行 g++
  3. 傳回含有可執行輸出內容和其他檔案的 DefaultInfo 供應器 而在執行階段期間提供使用的資源
  4. 傳回CcInfo提供者,其中含有從 目標及其依附元件

從 Bazel 的角度來看,g++ 和標準 C++ 程式庫也是輸入內容 。規則編寫者不僅必須考量使用者提供的資料 輸入到規則,而且執行所需的所有工具和程式庫 行動

在建立或修改任何規則之前,請務必熟悉 Bazel 的 建構階段。請務必瞭解 各階段 (載入、分析和執行) 的階段。這項工具也很實用 瞭解巨集來瞭解規則與 巨集。如要開始使用,請先參閱規則教學課程。 接著,請將這個頁面當做參考。

Bazel 本身就內建了一些規則。這些原生規則,例如 genrulefilegroup,提供部分核心支援。 您可以自行定義規則,新增對語言和工具的支援 但是 Bazel 無法原生支援

Bazel 提供可擴充模型,以便使用 Starlark 語言。這些規則以 .bzl 個檔案寫入, 可以直接從 BUILD 檔案載入。

定義自己的規則時,你必須決定規則支援哪些屬性 它如何產生輸出內容

該規則的 implementation 函式會定義其在操作期間發生的確切行為, 分析階段。這個函式不會執行任何 以及外部指令而是會註冊即將使用的動作 並建構該規則的輸出內容 。

建立規則

.bzl 檔案中,使用 rule 函式定義新的規則 並將結果儲存為全域變數呼叫 rule 會指定 屬性實作函式

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

這會定義名為 example_library規則種類

rule 的呼叫也必須指定規則會建立 可執行輸出內容 (使用 executable = True),或更具體地說明 測試執行檔 (包含 test = True)。如果是後者,則規則為測試規則。 且規則名稱的結尾必須是 _test

目標例項化

您可在 BUILD 檔案中載入並呼叫規則:

load('//some/pkg:rules.bzl', 'example_library')

example_library(
    name = "example_target",
    deps = [":another_target"],
    ...
)

每次呼叫建構規則都不會傳回任何值,但會產生定義 建立一項目標這稱為「執行個體化」規則。這會為 新的目標和目標屬性的值。

您也可以透過 Starlark 函式呼叫規則,並在 .bzl 檔案中載入規則。 呼叫規則的 Starlark 功能稱為「Starlark 巨集」。 Starlark 巨集最終必須從 BUILD 檔案呼叫,且只能 會在載入階段呼叫,如果 BUILD 檔案會評估以將目標執行個體化。

屬性

「屬性」是規則引數。屬性可以提供特定值給 目標的實作,或參照 建立依附關係圖

規則專屬的屬性 (例如 srcsdeps) 是透過傳送地圖來定義 從屬性名稱到結構定義 (使用 attr 建立) 模組) 至 ruleattrs 參數。 常見屬性,例如 所有規則會間接加入 namevisibility。其他資訊 會以隱含形式新增至 執行及測試規則。用途 以隱含方式加到規則的字典中,不能納入傳遞至 attrs

依附元件屬性

處理原始碼的規則通常會定義下列屬性 各種依附元件類型

  • srcs 會指定由目標動作處理的來源檔案。這通常 屬性結構定義會指定應使用哪些副檔名。 所處理的來源檔案標題檔案語言相關規則 一般而言,如果是由伺服器處理的標頭,請指定不同的 hdrs 屬性 目標與消費者
  • deps 會指定目標的程式碼依附元件。屬性結構定義應該 指定這些依附元件必須提供哪些「供應商」。(對於 例如,cc_library 提供 CcInfo)。
  • data 指定可在執行階段提供給任何執行檔的檔案 這取決於指定目標這樣可以允許任意檔案 。
example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = [".example"]),
        "hdrs": attr.label_list(allow_files = [".header"]),
        "deps": attr.label_list(providers = [ExampleInfo]),
        "data": attr.label_list(allow_files = True),
        ...
    },
)

這些是依附元件屬性的範例。任何指定下列屬性的屬性: 如使用 attr.label_listattr.label,或 attr.label_keyed_string_dict) 指定特定類型的依附元件 指定目標和其標籤 (或對應的 Label 物件) 列於該屬性中 已解析這些標籤的存放區,可能還有路徑 而不是根據已定義的目標做比較

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

在這個範例中,other_targetmy_target 的依附元件,因此 會先分析「other_target」。如果 目標依附元件圖

私人屬性和隱含依附元件

具有預設值的依附元件屬性會建立「隱含依附元件」。這項服務 是隱含的,因為這是使用者未達到的目標圖表的一部分 請在 BUILD 檔案中指定該檔案。隱含依附性非常適合以硬式編碼的方式 規則與工具之間的關係 (建構時間依附元件,例如 編譯器),因為使用者大部分的時間大多沒有指定 都會套用這個政策在規則的實作函式中,系統會將這 與其他依附元件相同

如何提供隱含依附元件,但不允許使用者 可以覆寫這個值,就可以為屬性命名,藉此將屬性設為 private 開頭為底線 (_)。私人屬性必須含有預設值 輕鬆分配獎金通常只有隱含私有屬性適合用來簡化 依附元件

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

在這個範例中,example_library 類型的每個目標都有隱含 依附元件取決於編譯器 //tools:example_compiler。這樣一來, example_library 的實作函式,可產生叫用 編譯器。開始時間 _compiler 是私有屬性,遵循 ctx.attr._compiler 一律會指向這項規則的所有目標中的 //tools:example_compiler 類型。或者,您也可以將屬性命名為 compiler,但不要加入 並保留預設值如此一來,使用者就能將 不同的編譯器,但不需要瞭解編譯器的 標籤。

隱含依附性通常用於 實作為規則實作如果工具來自 執行平台或其他存放區 規則應透過工具鍊取得該工具。

輸出屬性

輸出屬性,例如 attr.outputattr.output_list,宣告輸出檔案 並產生目標這與依附元件屬性的差異有兩種:

  • 用於定義輸出檔案目標,而非參照已定義的目標 如果其他位置需要使用資源 Compute Engine 有權終止工作
  • 輸出檔案目標取決於例項化的規則目標,而非 方針。

一般而言,只有在規則需要建立輸出內容時,才會使用輸出屬性 。如果規則 一個輸出屬性,通常命名為 outouts

建議您使用輸出屬性建立預先宣告的輸出內容, 可以直接仰賴或 透過指令列要求

實作函式

每項規則都需要 implementation 函式。系統會執行 進行的分析階段,並將 載入階段產生的目標圖表 要執行的動作。因此 無法實際讀取或寫入檔案

規則導入函式通常屬於私人性質 (以 底線)。一般情況下,這些變數的名稱與規則相同,但後置字元 只在 _impl

實作函式僅接受一個參數: 「規則內容」,通常命名為 ctx。他們會傳回 供應商

目標

在分析時,依附元件會顯示為 Target 如需儲存大量結構化物件 建議使用 Cloud Bigtable這些物件包含當 已執行目標的實作函式。

ctx.attr 有對應各個欄位名稱的欄位 依附元件屬性,包含 Target 物件,代表每個直接管道 依附於該屬性如果是 label_list 屬性,此為清單 Targets。如果是 label 屬性,這是指單一 TargetNone

目標的實作函式會傳回提供者物件清單:

return [ExampleInfo(headers = depset(...))]

但可以使用索引標記法 ([]) 存取這些項目,並將提供者類型設為提供者, 索引鍵。這些供應商可以是 Starlark 中定義的自訂供應商, Starlark 提供的原生規則供應商 全域變數

舉例來說,如果規則使用 hdrs 屬性取得標頭檔案,並提供 再加上目標及其消費者的編譯動作 資料收集方式如下:

def _example_library_impl(ctx):
    ...
    transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]

強烈不建議使用舊版結構體樣式,規則應該 錯誤遷移

檔案

檔案以 File 物件表示。由於 Bazel 這些物件無法在分析階段執行 直接讀取或寫入檔案內容而是會轉向行動 函式 (請參閱 ctx.actions) 來建立 動作圖表

File 可以是來源檔案或產生的檔案。每個產生的檔案 必須是一項動作的輸出內容。來源檔案不得為下列項目的輸出內容: 任何動作

每個依附元件屬性的對應欄位 ctx.files 包含所有預設輸出內容的清單 使用該屬性的依附元件:

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file 包含一個 FileNone: 規格設為 allow_single_file = True 的依附元件屬性。 ctx.executable 的運作方式與 ctx.file 相同,但只有 包含依附元件屬性的欄位,且規格設為 executable = True

宣告輸出內容

在分析階段,規則的實作函式可建立輸出內容。 由於所有標籤必須在載入階段已知,因此這些額外 的輸出則沒有標籤。輸出的 File 物件可使用 ctx.actions.declare_filectx.actions.declare_directory。 輸出的名稱通常以目標的名稱為基礎 ctx.label.name

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

針對預先宣告的輸出內容,例如為 輸出屬性,可改為擷取 File 物件 來自 ctx.outputs 的對應欄位。

動作

動作說明如何從一組輸入內容產生一組輸出內容, 例如「run gcc on hello.c and get hello.o」。建立動作時,Bazel 未立即執行指令它會以依附元件圖表註冊 因為動作可能會取決於其他動作的輸出內容。例如在 C 中 就必須在編譯器之後呼叫連結器。

建立動作的一般用途函式的定義如下: ctx.actions

ctx.actions.args可讓您有效率地 累積動作的引數這能避免在 執行時間:

def _example_library_impl(ctx):
    ...

    transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    inputs = depset(srcs, transitive = [headers])
    output_file = ctx.actions.declare_file(ctx.label.name + ".output")

    args = ctx.actions.args()
    args.add_joined("-h", headers, join_with = ",")
    args.add_joined("-s", srcs, join_with = ",")
    args.add("-o", output_file)

    ctx.actions.run(
        mnemonic = "ExampleCompile",
        executable = ctx.executable._compiler,
        arguments = [args],
        inputs = inputs,
        outputs = [output_file],
    )
    ...

這類動作會使用輸入檔案的清單或解碼器,並產生一份 (非空白) 清單, 輸出檔案這組輸入與輸出檔案必須在 分析階段:這可能取決於 屬性,包括依附元件的提供者,但不能仰賴 執行結果舉例來說,如果您的動作執行瞭解壓縮指令, ,並指定要加載哪些檔案 (在執行解壓縮前)。 會在內部建立數量不定檔案的動作,可以將這些動作納入 單一檔案 (例如 zip、tar 或其他封存格式)。

動作必須列出所有輸入內容。列出未使用的輸入內容 但效率較低

動作必須建立其所有輸出內容。他們可以寫入其他檔案 輸出內容中不會顯示的所有內容。所有宣告的輸出內容 必須由某些動作編寫

動作與純功能相當:應只依附於 避免存取電腦資訊、使用者名稱、時鐘 網路或 I/O 裝置 (讀取輸入和寫入輸出除外)。這是 因為系統會快取並重複使用輸出內容。

Bazel 會解析依附元件,並決定要執行哪些動作 此程序的第一步 是將程式碼簽入執行所有單元如果依附元件圖表中有循環,就會發生錯誤。建立中 動作不保證一定會執行,實際情況取決於是否 建構需要輸出內容

提供者

供應商是規則所公開的資訊, 依靠它這項資料可包含輸出檔案、程式庫和要傳遞的參數 或者任何目標消費者應該知道的事項 介紹生成式 AI 模型

由於規則的實作函式只能從 將目標執行個體化的立即依附元件,規則需要轉送任何 目標依附元件中的資訊,且必須由目標的 通常會累計到 depset 中。

目標的提供者是由 實作函式

舊版實作函式也能以舊版樣式編寫, 實作函式會傳回 struct,而非 以及提供者物件強烈建議不要採用這個樣式,規則應該設為 錯誤遷移

預設輸出裝置

目標的預設輸出是指在 會透過指令列要求建構目標。舉例來說 java_library 目標 //pkg:foo 的預設輸出為 foo.jar,因此, 都將由 bazel build //pkg:foo 指令建構

預設輸出內容是由以下項目的 files 參數指定: DefaultInfo

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

如果實作規則或 files 沒有傳回 DefaultInfo 未指定參數,DefaultInfo.files 會預設為所有值 預先宣告的輸出內容 (通常是透過 輸出產生的 屬性)。

執行動作的規則應該提供預設輸出內容,即使這些輸出內容 不必直接使用圖表中未顯示的動作 會刪減部分要求如果只有目標使用者會使用輸出內容, 系統不會在內建目標的情況下執行這些動作。這個 會讓偵錯更加困難,因為僅重新建構失敗的目標並不會 重現故障

執行檔案

執行檔案是目標在執行階段 (而非建構) 使用的一組檔案 時間)。在執行階段期間,Bazel 會建立 包含指向執行檔案符號連結的目錄樹狀結構。這會讓 環境,方便在執行階段期間存取執行檔案。

您可以在建立規則時手動新增執行檔案。 runfiles 方法可以建立 runfiles 物件 並傳遞至 ctx.runfiles DefaultInfo 上的 runfiles 參數。系統呼叫 系統會間接將執行檔規則新增至執行檔案。

某些規則會指定屬性 (通常命名為 data,其輸出內容會新增至 指定目標runfilesRunfile 也應合併從 data,以及 任何可能提供程式碼以供最終執行的屬性 (一般來說) srcs (可能包含 filegroup 個與關聯 data 的目標) 和 deps

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

自訂供應商

您可以使用 provider 定義供應商 用來表示規則相關資訊:

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields = {
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    },
)

這樣一來,規則實作函式就能建構並傳回供應器例項:

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
自訂供應商初始化

您可以使用自訂的 預先處理和驗證邏輯這能用來確保 供應商執行個體可以滿足某些不變體,或為使用者提供更簡潔的 API 取得執行個體

方法是將 init 回呼傳遞至 provider 函式。如果指定了這個回呼, provider() 的傳回類型變更為以下兩個值的元組:提供者 符號 (未使用 init 時) 一般回傳值;「原始」 建構函式。」

在此情況下,呼叫提供者符號,而不是直接傳回 新的例項,就會將引數一併轉送至 init 回呼。 回呼的傳回值必須是將欄位名稱 (字串) 對應至值的字典; 此物件可用於初始化新執行個體的欄位。請注意, 回呼可能具有任何簽章,且引數不符簽章 系統會將錯誤回報為直接叫用回呼。

相較之下,原始建構函式會略過 init 回呼。

以下範例使用 init 預先處理並驗證其引數:

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# Keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
    if not files_to_link and not allow_empty_files_to_link:
        fail("files_to_link may not be empty")
    all_headers = depset(_core_headers, transitive = headers)
    return {"files_to_link": files_to_link, "headers": all_headers}

ExampleInfo, _new_exampleinfo = provider(
    fields = ["files_to_link", "headers"],
    init = _exampleinfo_init,
)

接著,規則實作可能會將供應器例項化,如下所示:

ExampleInfo(
    files_to_link = my_files_to_link,  # may not be empty
    headers = my_headers,  # will automatically include the core headers
)

原始建構函式可用來定義額外的公用工廠函式 不會經過 init 邏輯的操作例如 exampleinfo.bzl 可以定義:

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

一般來說,原始建構函式會繫結至名稱開頭為 因此使用者程式碼無法載入_new_exampleinfo 產生任意提供者執行個體

init 的另一個用途是防止使用者呼叫供應器 符號,並強制要求他們改用工廠函式:

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

可執行的規則和測試規則

可執行的規則會定義可透過 bazel run 指令叫用的目標。 測試規則是特殊的可執行規則,目標同樣可以 透過 bazel test 指令叫用。可執行和測試規則的建立依據如下 設定對應的 executable 或 在對 rule 呼叫中呼叫 Truetest 引數:

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

測試規則的名稱必須以 _test 結尾。(通常也要測試目標名稱 按照慣例在 _test 結尾,但這並非強制規定)。非測試規則不得 都有這個尾碼

這兩種規則必須產生一個可執行的輸出檔案 (不一定可以 runtest 指令叫用這些片段。說明 要將哪個規則輸出做為這個執行檔使用,並將該輸出內容做為 傳回的 DefaultInfoexecutable 引數 。該 executable 會新增至規則的預設輸出內容中 ( 但不必同時傳遞至 executablefiles)。這個過程隱含 新增到 runfiles 中:

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

產生這個檔案的動作必須在檔案中設定可執行檔的位元。適用對象 ctx.actions.runctx.actions.run_shell 應執行這個動作 由動作叫用的基礎工具。換 ctx.actions.write 動作,請傳遞 is_executable = True

如同舊版行為,執行檔規則具有 特殊的 ctx.outputs.executable 預先宣告輸出內容這個檔案做為 預設可執行檔 (如果未使用 DefaultInfo 指定);不得 或以其他方式使用。這項輸出機制不支援,因此已淘汰 自訂執行檔的名稱。

請參閱 執行規則測試規則

可執行的規則測試規則 除了為 所有規則。預設值 隱含新增的屬性無法變更,不過這可以 方法是在 Starlark 巨集中納入私人規則,將 預設:

def example_test(size = "small", **kwargs):
  _example_test(size = size, **kwargs)

_example_test = rule(
 ...
)

執行檔案位置

使用 bazel run (或 test) 執行可執行目標時, runfiles 目錄位在可執行檔旁。路徑如下:

# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

runfiles 目錄下 File 的路徑會對應至 File.short_path

bazel 直接執行的二進位檔會相鄰 runfiles 目錄內。不過,「從」執行檔案呼叫的二進位檔無法 相同的假設為緩解此問題,每個二進位檔都應提供一種方法: 使用環境或指令列,接受其執行檔案根目錄做為參數 引數或旗標這可讓二進位檔傳遞正確的標準執行檔根目錄 到它呼叫的二進位檔。如未設定,二進位檔能夠猜到 會呼叫第一個二進位檔,找出相鄰的 runfiles 目錄。

進階主題

要求輸出檔案

單一目標可以有多個輸出檔案。如果 bazel build 指令顯示為 因此系統會將指定該指令的目標輸出內容視為 提出要求。Bazel 只會建構這些要求的檔案及其所取得的檔案 完全或間接取決於(就動作圖而言,只有 Bazel 這會執行可做為 要求的檔案)。

除了預設輸出內容之外,任何預先宣告的輸出內容都可以 明確要求規則可以指定預先宣告的 輸出屬性的輸出內容。在這種情況下,使用者 將規則例項化時,會明確選擇輸出的標籤。為了達成 針對輸出屬性的 File 物件,請使用對應的 ctx.outputs 的屬性。規則可以 以隱含方式定義預先宣告的輸出內容,是以 加上目標名稱,但這項功能已淘汰。

除了預設輸出之外,還有「輸出群組」,這是集合 可能同時要求的輸出檔案這些資訊都可以透過 --output_groups。適用對象 舉例來說,如果目標 //pkg:mytarget 所屬的規則類型為 debug_files 輸出群組,執行 bazel build //pkg:mytarget --output_groups=debug_files 即可建立這些檔案。未預先宣告的輸出內容沒有標籤 只有在預設輸出內容或輸出內容中 群組。

您可以使用 OutputGroupInfo 提供者。請注意,有別於許多 內建供應器,OutputGroupInfo 可以採用任意名稱的參數 定義使用該名稱的輸出群組:

def _example_library_impl(ctx):
    ...
    debug_file = ctx.actions.declare_file(name + ".pdb")
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        OutputGroupInfo(
            debug_files = depset([debug_file]),
            all_files = depset([output_file, debug_file]),
        ),
        ...
    ]

此外,與大多數提供者不同,OutputGroupInfo 也可以由 aspect 以及要套用該切面的規則目標,例如 前提是這些輸出群組沒有定義相同的輸出群組在這個例子中 會合併。

請注意,OutputGroupInfo 通常不應用於表示特定排序 檔案和消費者的動作定義 規則專屬供應商

設定

假設您要為不同架構建構 C++ 二進位檔。 建構可能相當複雜,且包含多個步驟有些中階 編譯器和程式碼產生器等二進位檔 執行平台 (可以是主機、 或遠端執行程式)。有些二進位檔 (例如最終輸出) 必須針對 目標架構

因此,Bazel 採用「設定」的概念和轉場效果 最頂層的目標 (在指令列上所要求的目標) 是內建的 「target」而應在執行平台上執行的工具 內建於「exec」此外還會從 0 自動調整資源配置 您完全不必調整資源調度設定規則可能會根據不同條件產生不同動作 設定,例如變更先前傳遞的 CPU 架構 提供給編譯器在某些情況下, 儲存空間設定在這種情況下,系統會進行分析,並在可能會 以便重複使用

根據預設,Bazel 會使用與 也就是目標本身,也就是沒有轉場效果如果依附元件是 有助於建構目標所需的工具,相應的屬性 指定執行 exec 設定的轉換作業這會導致工具及其 為執行平台建構的依附元件。

對於每個依附元件屬性,您可以使用 cfg 判斷是否依附元件 應以相同的設定建構,或是轉換至 exec 設定。 如果依附元件屬性含有 executable = True 標記,則必須設定 cfg 。這是為了避免不小心建立錯誤的工具 此外還會從 0 自動調整資源配置 您完全不必調整資源調度設定 查看範例

一般來說, 也能使用相同的設定

在建構作業中執行的工具 (例如編譯器或程式碼產生器) 均應針對 exec 設定進行建構在本例中,請將 cfg = "exec" 指定為 屬性。

否則,在執行階段使用的執行檔 (例如測試內容) 必須 為目標設定建構容器在本例中,請將 cfg = "target" 指定為 屬性。

cfg = "target" 實際上不會有任何動作,只是單純的 能協助規則設計師明確表達他們的目的發生executable = False時, 這表示 cfg 為選用項目,只有在真正能提升可讀性時才設定此設定。

您也可以使用 cfg = my_transition使用者定義的轉場效果 讓規則作者能大幅彈性變更設定 缺點 放大建構圖表,使其較不易理解

注意:Bazel 以往並沒有執行平台的概念。 所有建構動作都視為在主體機器上執行Bazel 6.0 以下版本建立了專屬「主機」代表該物件 如果看到「主機」一詞或先前的說明文件 參照。建議您使用 Bazel 6.0 以上版本,以免發生這種額外概念 同時免除不必要的負擔

設定片段

規則可存取 設定片段,例如 cppjava。不過,所有必要的片段都必須在 以避免存取錯誤:

def _impl(ctx):
    # Using ctx.fragments.cpp leads to an error since it was not declared.
    x = ctx.fragments.java
    ...

my_rule = rule(
    implementation = _impl,
    fragments = ["java"],      # Required fragments of the target configuration
    ...
)

一般來說,執行檔案樹狀結構中檔案的相對路徑與 該檔案在來源樹狀結構或產生的輸出樹狀結構中的相對路徑。如果這些 而這些項目因某些原因而不同,您可以指定 root_symlinkssymlinks 引數。root_symlinks 是指向 檔案,其中路徑與執行檔案目錄的根目錄相關。 symlinks 字典相同,但路徑會用 主要工作區的名稱 (「不是」包含 目前的目標)。

    ...
    runfiles = ctx.runfiles(
        root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
        symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
    )
    # Creates something like:
    # sometarget.runfiles/
    #     some/
    #         path/
    #             here.foo -> some_data_file2
    #     <workspace_name>/
    #         some/
    #             path/
    #                 here.bar -> some_data_file3

如果使用 symlinksroot_symlinks,請注意不要對應兩種 將檔案移到執行檔案樹狀結構的相同路徑上這會導致建構失敗 傳回說明衝突的錯誤訊息如要修正,請修改您的 用於移除衝突的 ctx.runfiles 引數。這項檢查將完成 使用規則的任何目標,以及符合這些規則的任何類型目標 目標。如果你的工具可能會常被用於這類用途,那就特別風險。 由其他工具提供符號連結名稱在工具的所有執行檔案中都不得重複 所有依附元件

程式碼涵蓋率

執行 coverage 指令時, 版本可能需要針對特定目標新增涵蓋範圍檢測。 也會收集檢測的來源檔案清單。子集 而這些目標取決於標記所控制的目標 --instrumentation_filter。 系統會排除測試目標,除非 --instrument_test_targets

如果規則實作會在建構期間新增涵蓋率檢測,就需要 因為這在實作函式中能反映這一點 ctx.coverage_instrumented 傳回 如果應對目標來源進行檢測,則在涵蓋率模式下使用 True

# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
  # Do something to turn on coverage for this compile action

一律必須在涵蓋率模式 (無論是目標的來源) 啟用的邏輯 可藉由檢測系統 ctx.configuration.coverage_enabled.

如果規則在編譯前直接納入依附元件的來源 (例如標頭檔案),如果 依附元件來源應進行檢測:

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
    (ctx.coverage_instrumented() or
     any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
    # Do something to turn on coverage for this compile action

規則也應提供相關資訊,說明哪些屬性與 涵蓋率,透過 InstrumentedFilesInfo 供應商 coverage_common.instrumented_files_infoinstrumented_files_infodependency_attributes 參數應該會列出 所有執行階段依附元件屬性,包括 deps 等程式碼依附元件 資料依附元件,例如 datasource_attributes 參數應該會列出 如果可以新增涵蓋範圍檢測,則規則的來源檔案屬性:

def _example_library_impl(ctx):
    ...
    return [
        ...
        coverage_common.instrumented_files_info(
            ctx,
            dependency_attributes = ["deps", "data"],
            # Omitted if coverage is not supported for this rule:
            source_attributes = ["srcs", "hdrs"],
        )
        ...
    ]

如果未傳回 InstrumentedFilesInfo,系統會為每個參數建立預設值 未設定的非工具依附元件屬性cfg 變更為 "exec"。英吋 dependency_attributes。(這並不是理想的行為,因為這會將屬性 類似 dependency_attributes 中的 srcs,而不是 source_attributes,但是 可避免對 Pod 中的所有規則 依附元件鏈結)。

驗證動作

有時您需要驗證建構作業 驗證作業所需的資訊僅適用於構件 (來源檔案或產生的檔案)。因為這項資訊儲存在構件中 規則無法讀取,因此規則無法在分析時進行這項驗證 檔案。相反地,動作必須在執行時進行這項驗證。時間 驗證失敗,動作就會失敗,因而建構失敗。

靜態分析、程式碼檢查、 依附元件和一致性檢查,以及樣式檢查。

驗證動作也有助於移動零件,進而改善建構成效 將構件建構成獨立動作時不需要執行的動作。 舉例來說,如果單一動作會編譯和程式碼檢查, 程式碼分割為編譯動作和程式碼檢查動作 動作可作為驗證動作執行,並與其他動作並行執行。

這些「驗證動作」而且通常不會產生在其他地方使用的內容 物件中,因為他們只需要斷言輸入內容。這個 但還是有問題:如果驗證動作沒有產生 在建構中的其他位置使用,規則如何使規則執行? 以往,這個方法是將驗證動作輸出為空白 手動將該輸出內容新增至 動作:

這種做法雖然可行,因為 Bazel 一律會在編譯期間執行驗證動作 但這會產生重大缺點:

  1. 驗證動作在版本的重要路徑中。因為 Bazel 認為執行編譯動作需要空白輸出,就會執行 驗證動作會先予以忽略,但編譯動作會忽略輸入內容。 這樣可以減少平行處理,並減緩建構作業的速度。

  2. 如果建構中的其他動作可能執行,而非執行 編譯動作,然後將驗證動作的空白輸出內容加入 以及這些動作 (例如 java_library 的來源 jar 輸出內容)。這是 也會發生問題 ,之後就會意外遺漏空白驗證輸出內容。

如要解決這類問題,請使用「驗證輸出群組」。

驗證輸出群組

「驗證輸出群組」是一個輸出群組,其用途是保留 未使用驗證動作的輸出內容,因此不必以人為方式 都會新增到其他動作的輸入來源中

這個群組很特別,因為無論 --output_groups 標記的值,以及無論目標如何 可能相依 (例如:在指令列上、依附元件 目標的隱含輸出)。請注意,一般快取和成效增幅 仍然適用:如果驗證動作的輸入內容沒有變更,且 先前成功通過驗證,驗證動作就不會 此程序的第一步 是將程式碼簽入執行所有單元測試的存放區中

使用這個輸出群組時,驗證動作仍會輸出一些檔案。 即使空無一人也不例外這可能需要包裝一些通常不會 建立輸出內容,藉此建立檔案。

在下列三種情況下,目標的驗證動作無法執行:

  • 目標依循工具
  • 當目標依賴隱含依附元件時 (例如 以「_」開頭的屬性
  • 已在 exec 設定中建構目標時。

假設這些目標都有各自的 建立獨立的建構作業和測試,找出任何驗證失敗情形。

使用驗證輸出群組

「驗證輸出群組」的名稱是 _validation,其用途與任何其他相同 輸出群組:

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")
  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
    outputs = [validation_output],
    executable = ctx.executable._validation_tool,
    arguments = [validation_output.path],
  )

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"
    ),
  }
)

請注意,驗證輸出檔案不會新增至 DefaultInfo 或 其他動作輸入輸入內容這個規則種類目標的驗證動作 如果目標相依於標籤或目標的 隱含輸出內容直接或間接依附於。

通常很重要的是,驗證動作的輸出內容只會納入 驗證輸出群組,也不會新增至其他動作的輸入內容,例如 這樣可以打敗平行處理量的增益請注意,Bazel 不會 執行任何特殊檢查因此,您應該 系統不會將驗證動作的輸出內容新增到 測試 Starlark 規則例如:

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

驗證動作旗標

執行驗證動作是由 --run_validations 指令列控管 旗標,預設為 true。

已淘汰的功能

已淘汰的預先宣告輸出內容

使用預先宣告的輸出內容有兩種已淘汰的方法:

  • ruleoutputs 參數會指定 輸出屬性名稱和用於產生內容的字串範本之間的對應關係 預先宣告的輸出標籤偏好使用未宣告的輸出內容,以及 明確地將輸出新增至 DefaultInfo.files。使用規則目標 使用標籤做為使用輸出 (而非預先宣告) 的規則輸入標籤 輸出內容的標籤

  • 針對可執行規則ctx.outputs.executable 會參照 連線至預先宣告的可執行檔輸出,名稱與規則目標相同。 最好明確宣告輸出內容,例如使用 ctx.actions.declare_file(ctx.label.name),並確認下列指令 產生執行檔時,系統會設定執行檔的權限。明確 將執行檔的輸出內容傳遞至 DefaultInfoexecutable 參數。

應執行的檔案功能

ctx.runfilesrunfiles 類型具有一系列複雜的功能,其中有許多功能因傳統因素而保留。 以下建議有助於降低複雜度:

  • 避免使用 collect_datacollect_default 模式 ctx.runfiles。這些模式會間接收集 在特定硬式編碼依附元件邊緣中執行檔案,以令人混淆的方式執行。 請改為使用以下來源的 filestransitive_files 參數新增檔案: ctx.runfiles,或將依附元件從依附元件中合併 runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)

  • 避免使用 data_runfilesdefault_runfiles DefaultInfo 建構函式。請改為指定 DefaultInfo(runfiles = ...)。 「預設」之間的差異和「data」 舊版原因。例如,有些規則會將預設輸出內容 data_runfiles,但不是 default_runfiles。不使用 data_runfiles,規則「同時」包含預設輸出內容並合併 default_runfiles 來自提供執行檔案的屬性 (通常是 data)。

  • DefaultInfo 擷取 runfiles 時 (通常僅適用於合併 會在目前規則及其依附元件之間傳輸檔案),請使用 DefaultInfo.default_runfiles不是 DefaultInfo.data_runfiles

從舊版供應商遷移

以往 Bazel 供應商是 Target 物件上的簡單欄位。他們 可透過點號運算子存取,且是由將 欄位插入 在 struct 中,由規則的 實作函式,而不是提供者物件清單:

return struct(example_info = struct(headers = depset(...)))

這類提供者可從 Target 物件的對應欄位擷取:

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

這個樣式已淘汰,不應用於新程式碼;請參閱下方的 有助於完成遷移的資訊新的供應商機制會避免名稱 的衝突。也支援隱藏資料,方法是要求任何程式碼存取 以使用提供者符號進行擷取。

目前仍可繼續支援舊版供應商。規則可同時傳回 舊版和新型供應商的供應器如下:

def _old_rule_impl(ctx):
  ...
  legacy_data = struct(x = "foo", ...)
  modern_data = MyInfo(y = "bar", ...)
  # When any legacy providers are returned, the top-level returned value is a
  # struct.
  return struct(
      # One key = value entry for each legacy provider.
      legacy_info = legacy_data,
      ...
      # Additional modern providers:
      providers = [modern_data, ...])

如果 dep 是此規則例項的結果 Target 物件, 其內容可用 dep.legacy_info.xdep[MyInfo].y

除了 providers 以外,傳回的結構也可以是其他 欄位具有特殊意義 (因此不會建立對應的舊版 供應商):

  • filesrunfilesdata_runfilesdefault_runfilesexecutable 對應於 DefaultInfo。但不可指定任何一個 同時傳回 DefaultInfo 供應器。

  • output_groups 欄位採用結構體值,並對應至 OutputGroupInfo

在規則的 provides 宣告中,以及 providers 依附元件宣告 屬性、舊版提供者是以字串的形式傳入,而現代的供應器則 以 Info 符號傳入。請務必從字串改為符號 執行遷移適用於較複雜或大型規則集,且難以更新 將所有規則統一排序,因此如果在測試時依循此順序 步驟:

  1. 修改產生舊版供應器的規則,以同時產生舊版供應商 和現代提供者對於宣告 會傳回舊版供應商,請更新該宣告,以同時包含 舊版和新型供應商的服務。

  2. 修改使用舊版供應商的規則,改為使用 現代供應商如有任何屬性宣告需要舊版供應商 更新這些元件,改為需要現代供應商。您也可以 在步驟 1 中,請消費者接受或要求 供應商:使用 hasattr(target, 'foo'),或是使用 FooInfo in target 的新供應商。

  3. 將舊版供應商從所有規則中完全移除。