使用巨集建立自訂動詞

回報問題 查看來源

與 Bazel 的日常互動主要透過幾個指令執行:buildtestrun。但是,有時這些感覺會受到限制:您可能會想將套件推送至存放區、為使用者發布說明文件,或使用 Kubernetes 部署應用程式。不過,Bazel 沒有 publishdeploy 指令,可以在何處執行這些動作?

bazel run 指令

Bazel 注重遺傳性、可重現性和成效增幅,這表示 buildtest 指令對於上述工作沒有幫助。這些動作可能會在限制網路存取權的沙箱中執行,而且不保證會每 bazel build 重新執行。

請改為使用 bazel run:您「想」具有副作用的工作。Bazel 使用者習慣建立執行檔的規則,而規則作者可以遵循一組通用的模式,將其延伸至「自訂動詞」。

野外:Rule_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,
)

最後,定義下列巨集,同時為上述兩項規則建立目標:

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 規則的實作方式。這類動作通常都會編寫啟動器殼層指令碼。此方法通常會使用 ctx.actions.expand_template 編寫非常簡單的殼層指令碼,在此情況下,會叫用具有主要目標輸出內容的路徑的發布者二進位檔。如此一來,發布商實作項目可保持一般,_sphinx_site 規則便可只產生 HTML,而這個小型指令碼就是合併兩者時所需的一切。

rules_k8s 中,這的確是 .apply 的功用:expand_template 會根據 apply.sh.tpl 編寫非常簡單的 Bash 指令碼,後者會透過主要目標的輸出內容執行 kubectl。然後,這個指令碼就能使用 bazel run :staging.apply 建構及執行,進而有效為 k8s_object 目標提供 k8s-apply 指令。