2022 年 BazelCon 将于 11 月 16 日至 17 日在纽约和线上举办。
立即报名!

使用宏创建自定义口音

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

与 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",
)

staging 目标上使用 bazel build 时,k8s_object 规则会构建标准 Kubernetes YAML 文件。不过,其他目标也是由名称为 staging.apply:staging.delete 等的 k8s_object 宏创建的。这些构建脚本将执行这些操作,使用 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"],
)

在此示例中,创建一个“文档”目标,就像该宏是标准的 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 编写基于以下任务的非常简单的 Bash 脚本:apply.sh.tplkubectl替换为主要目标的输出。然后,您可以使用 bazel run :staging.apply 构建和运行此脚本,从而有效地为 k8s_object 目标提供 k8s-apply 命令。