與 Bazel 的日常互動主要透過幾個指令:build
、test
和 run
。不過,這些功能有時可能不夠用:您可能想將套件推送至存放區、發布使用者文件,或是使用 Kubernetes 部署應用程式。但 Bazel 沒有 publish
或 deploy
指令,這些動作該如何執行?
bazel run 指令
Bazel 著重於密封性、可重現性和漸進性,因此 build
和 test
指令不適用於上述工作。這些動作可能會在沙箱中執行,網路存取權受限,且不保證每次 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",
)
k8s_object
規則會在 staging
目標上使用 bazel build
時,建構標準 Kubernetes YAML 檔案。不過,額外目標也是由 k8s_object
巨集建立,名稱類似 staging.apply
和 :staging.delete
。這些建構指令碼會執行這些動作,並在以 bazel run
staging.apply
執行時,行為與我們的 bazel k8s-apply
或 bazel
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 8 之前的 Bazel 版本,請改為定義舊版巨集:
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
這類動作通常會編寫 launcher shell 指令碼。
這個方法通常需要使用 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
指令。