与 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",
)
对 staging
目标使用 bazel build
时,k8s_object
规则会构建标准 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,
)
最后,定义以下宏以同时为上述两条规则创建目标:
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
编写一个非常简单的 shell 脚本,在本例中,使用主目标输出的路径调用发布商二进制文件。这样一来,发布商实现就可以保持通用,_sphinx_site
规则只能生成 HTML,并且只需结合使用以下小型脚本即可结合使用两者。
这在 rules_k8s
中确实是 .apply
的作用:expand_template
根据 apply.sh.tpl
编写了一个简单的 Bash 脚本,该脚本使用主目标的输出运行 kubectl
。然后,您可以使用 bazel run :staging.apply
构建并运行此脚本,这实际上为 k8s_object
目标提供了 k8s-apply
命令。