使用巨集建立自訂動詞

回報問題 查看原始碼 Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

您每天與 Bazel 互動時,主要會使用以下幾個指令:buildtestrun。不過,有時這些功能可能會讓您感到受限:您可能想將套件推送至存放區、為使用者發布文件,或透過 Kubernetes 部署應用程式。但 Bazel 沒有 publishdeploy 指令,這些動作要如何執行?

bazel run 指令

Bazel 著重於密封性、可重現性和增量性,因此 buildtest 指令對上述工作沒有幫助。這些動作可能會在沙箱中執行,網路存取權受限,且無法保證會在每次 bazel build 中重新執行。

請改為使用 bazel run:這是您希望有副作用的工作任務的強大工具。Bazel 使用者習慣建立可執行檔的規則,規則作者可以遵循一組常見的模式,將這項作業擴展至「自訂動詞」。

實際應用:rules_k8s

舉例來說,請考慮 rules_k8s,這是 Bazel 的 Kubernetes 規則。假設您有以下目標:

# BUILD file in //application/k8s
k8s_object(
    name = "staging",
    kind = "deployment",
    cluster = "testing",
    template = "deployment.yaml",
)

bazel build 用於 staging 目標時,k8s_object 規則會建構標準 Kubernetes YAML 檔案。不過,其他目標也是由 k8s_object 巨集建立,其名稱為 staging.apply:staging.delete。這些指令碼會建立用於執行這些動作的指令碼,並在使用 bazel run staging.apply 執行時,會像我們自己的 bazel k8s-applybazel k8s-delete 指令一樣運作。

另一個範例:ts_api_guardian_test

您也可以在 Angular 專案中看到這個模式。ts_api_guardian_test 巨集會產生兩個目標。第一個是標準 nodejs_test 目標,可將部分產生的輸出內容與「金」檔案 (也就是包含預期輸出的檔案) 進行比較。您可以使用一般 bazel test 叫用作業來建構及執行此類型。在 angular-cli 中,您可以使用 bazel test //etc/api:angular_devkit_core_api 執行這類目標

隨著時間的推移,這個黃金檔案可能需要基於正當理由進行更新。手動更新這項作業相當繁瑣且容易出錯,因此這個巨集也提供 nodejs_binary 目標,可更新金文件,而非與其進行比較。實際上,您可以根據呼叫方式,編寫相同的測試指令碼,以便在「驗證」或「接受」模式下執行。這會遵循您已學到的相同模式:沒有原生 bazel test-accept 指令,但可以使用 bazel run //etc/api:angular_devkit_core_api.accept 達到相同效果。

這個模式相當強大,而且一旦您學會辨識這種模式,就會發現這種模式相當常見。

調整自訂規則

巨集是這個模式的核心。巨集的用法與規則類似,但可以建立多個目標。通常,它們會建立具有指定名稱的目標,執行主要建構動作:可能會建構一般二進位檔、Docker 映像檔或原始碼封存檔。在這個模式中,系統會建立其他目標,根據主要目標的輸出內容產生執行副作用的指令碼,例如發布產生的二進位檔或更新預期的測試輸出內容。

為說明這項功能,我們將假設有個規則,可透過 Sphinx 產生網站,並使用巨集建立額外的目標,讓使用者在準備就緒時發布網站。請考慮使用 Sphinx 產生網站的現有規則:

_sphinx_site = rule(
     implementation = _sphinx_impl,
     attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)

接著,請考慮下列規則,這個規則會建立指令碼,並在執行時發布產生的網頁:

_sphinx_publisher = rule(
    implementation = _publish_impl,
    attrs = {
        "site": attr.label(),
        "_publisher": attr.label(
            default = "//internal/sphinx:publisher",
            executable = True,
        ),
    },
    executable = True,
)

最後,請定義下列符號巨集 (適用於 Bazel 8 以上版本),為上述兩個規則一併建立目標:

def _sphinx_site_impl(name, visibility, srcs, **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML. We
    # set `visibility = visibility` to make it visible to callers of the
    # macro.
    _sphinx_site(name = name, visibility = visibility, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above. We don't want it to be visible to callers of
    # our macro, so we omit visibility for it.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

sphinx_site = macro(
    implementation = _sphinx_site_impl,
    attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
    # Inherit common attributes like tags and testonly
    inherit_attrs = "common",
)

或者,如果您需要支援舊版 Bazel (低於 Bazel 8),則應改為定義舊版巨集:

def sphinx_site(name, srcs = [], **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML.
    _sphinx_site(name = name, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

BUILD 檔案中,使用巨集,就好像它只會建立主要目標一樣:

sphinx_site(
    name = "docs",
    srcs = ["index.md", "providers.md"],
)

在這個範例中,系統會建立「docs」目標,就像巨集是標準的單一 Bazel 規則一樣。建構時,規則會產生一些設定,並執行 Sphinx 產生 HTML 網站,以便進行手動檢查。不過,系統也會建立額外的「docs.publish」目標,用來建構用於發布網站的指令碼。檢查主要目標的輸出內容後,您可以使用 bazel run :docs.publish 將其發布供大眾使用,就像虛構的 bazel publish 指令一樣。

_sphinx_publisher 規則的實作方式可能不太明顯。這類動作通常會寫入啟動器 Shell 指令碼。這個方法通常會使用 ctx.actions.expand_template 編寫非常簡單的 Shell 指令碼,在本例中,您可以使用主要目標輸出內容的路徑,叫用發布者二進位檔。這樣一來,發布器實作項目可保持通用性,_sphinx_site 規則只會產生 HTML,而這個小腳本就是結合這兩者的必要條件。

rules_k8s 中,.apply 確實會執行以下操作:expand_template 會根據 apply.sh.tpl 編寫非常簡單的 Bash 指令碼,並使用主要目標的輸出內容執行 kubectl。接著,您可以使用 bazel run :staging.apply 建構並執行這個指令碼,為 k8s_object 目標提供 k8s-apply 指令。