相约 2023 年 BazelCon 将于 10 月 24 日至 25 日在 Google 慕尼黑举办!了解详情

创建永久性工作器

报告问题 查看源代码

持久性工作器可以加快您的构建速度。如果您的 build 中有重复的操作,并且启动成本较高或者可以从交叉操作缓存中受益,您可能需要实现自己的持久性工作器来执行这些操作。

Bazel 服务器使用 stdin/stdout 与工作器通信。它支持使用协议缓冲区或 JSON 字符串。

工作器实现包括两个部分:

构建工作器

持久性工作器可以支持几项要求:

  • 它会从其 stdin 读取 WorkRequest
  • 它会将 WorkResponse(且只有 WorkResponse)写入其 stdout
  • 它接受 --persistent_worker 标志。封装容器必须识别 --persistent_worker 命令行标志,并且仅在传递该标志时使自身保持持久性,否则它必须执行一次性编译并退出。

如果您的程序符合这些要求,则可用作持久工作器!

工作请求

WorkRequest 包含工作器的参数列表、表示工作器可访问的输入的路径摘要对(不强制执行,但您可以使用此信息进行缓存)以及请求 ID(对于单工工作器,请求 ID 为 0)。

注意:虽然协议缓冲区规范使用“蛇形大小写”(request_id),但 JSON 协议使用“驼峰式大小写”(requestId)。本文档在 JSON 示例中使用驼峰式大小写,但在讨论该字段时不考虑蛇形大小写(无论协议如何)。

{
  "arguments" : ["--some_argument"],
  "inputs" : [
    { "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
    { "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
 ],
  "requestId" : 12
}

可选的 verbosity 字段可用于从工作器中请求额外的调试输出。输出内容以及如何输出完全取决于工作器。值越高,输出越详细。将 --worker_verbose 标志传递给 Bazel 会将 verbosity 字段设置为 10,但您也可以针对较小的输出数量手动使用较小或较大的值。

只有支持多重沙盒的工作器才会使用可选的 sandbox_dir 字段。

工作回复

WorkResponse 包含一个请求 ID、一个零或非零退出代码,以及一个描述处理或执行请求时遇到的任何输出的输出字符串。output 字段包含简短的说明;完整日志可以写入工作器的 stderr。由于工作器只能将 WorkResponses 写入 stdout,因此工作器通常会将其使用的任何工具的 stdout 重定向到 stderr

{
  "exitCode" : 1,
  "output" : "Action failed with the following message:\nCould not find input
    file \"/path/to/my/file/1\"",
  "requestId" : 12
}

根据 protobuf 的标准,所有字段均为选填。但是,Bazel 要求 WorkRequest 和对应的 WorkResponse 具有相同的请求 ID,因此如果请求 ID 为非零,则必须指定请求 ID。这是一个有效的 WorkResponse

{
  "requestId" : 12,
}

request_id 为 0 表示“单一复用”请求,在此请求无法与其他请求并行处理时使用。服务器保证指定工作器仅接收 request_id 0 或仅 request_id 大于零的请求。系统会以串行形式发送单一复用请求,例如,如果服务器在收到响应之前未发送其他请求(取消请求除外,请参阅下文)。

备注

  • 每个协议缓冲区前面都是其长度,格式为 varint(请参阅 MessageLite.writeDelimitedTo())。
  • JSON 请求和响应前面没有大小指示器。
  • JSON 请求采用与 protobuf 相同的结构,但使用标准 JSON,并且对所有字段名称使用驼峰式大小写。
  • 为了保持与 protobuf 相同的向后和向前兼容性属性,JSON 工作器必须容忍这些消息中的未知字段,并为缺少的值使用 protobuf 默认值。
  • Bazel 将请求存储为 protobuf,并使用 protobuf 的 JSON 格式将其转换为 JSON

取消

工作器可以选择允许在其完成之前取消工作请求。这在与动态执行有关的情况下特别有用,其中动态执行经常会被较快的远程执行中断。若要允许取消,请将 supports-worker-cancellation: 1 添加到 execution-requirements 字段(见下文)并设置 --experimental_worker_cancellation 标志。

取消请求是设置了 cancel 字段的 WorkRequest(同样,取消请求是设置了 was_cancelled 字段的 WorkResponse)。取消请求或取消响应中必须包含的唯一字段必须是 request_id,表示要取消的请求。对于单工,request_id 字段将为 0;对于多工工作器,之前发送的 WorkRequest 为非 0 request_id。服务器可能会针对工作器已响应的请求发送取消请求,在这种情况下,必须忽略取消请求。

无论是否取消,每条未取消的 WorkRequest 消息都必须只回答一次。服务器发送取消请求后,工作器可能会返回 WorkResponse,并将 request_id 设为 was_cancelled 字段设为 true。您也可以使用常规 WorkResponse 发送方式,但 outputexit_code 字段将被忽略。

收到 WorkRequest 的响应后,工作器不得触摸其工作目录中的文件。服务器可以随意清理文件,包括临时文件。

创建使用工作器的规则

您还需要创建一条规则来生成工作器要执行的操作。创建使用工作器的 Starlark 规则就像创建任何其他规则一样。

此外,规则需要包含对工作器本身的引用,并且其生成的操作有一些要求。

引用工作器

使用工作器的规则需要包含一个引用工作器本身的字段,因此您需要创建一个 \*\_binary 规则实例来定义工作器。如果您的工作器名为 MyWorker.Java,那么关联的规则可能如下所示:

java_binary(
    name = "worker",
    srcs = ["MyWorker.Java"],
)

此操作会创建“工作器”标签,该标签代表工作器二进制文件。然后,您将定义使用工作器的规则。此规则应定义引用工作器二进制文件的属性。

如果您构建的工作器二进制文件位于名为“work”的软件包中(位于 build 的顶层),那么此属性可以定义如下:

"worker": attr.label(
    default = Label("//work:worker"),
    executable = True,
    cfg = "exec",
)

cfg = "exec" 表示应将工作器构建到在执行平台上运行,而不是在目标平台上运行(即在构建期间将工作器用作工具)。

工作操作要求

使用工作器的规则会创建供工作器执行的操作。这些操作有几项要求。

  • " arguments"字段。接受一个字符串列表,除最后一个字符串外,其余部分都是启动时传递给工作器的参数。“参数”列表中的最后一个元素是 flag-file (@-preceded) 参数。工作器根据每个 WorkRequest 从指定的 flagfile 中读取参数。您的规则可以将工作器的非启动参数写入此标志文件中。

  • "execution-requirements" 字段,用于接受包含 "supports-workers" : "1" 和/或 "supports-multiplex-workers" : "1" 的字典。

    对于发送到工作器的所有操作,“参数”和“执行要求”字段均为必填字段。此外,应由 JSON 工作器执行的操作需要在执行要求字段中包含 "requires-worker-protocol" : "json""requires-worker-protocol" : "proto" 也是一个有效的执行要求,但对 proto 工作器而言并不是必需的,因为它们是默认选项。

    您也可以在执行要求中设置 worker-key-mnemonic。如果要将可执行文件重复用于多种操作类型,并希望区分此工作器的操作,此方法可能很有用。

  • 在操作过程中生成的临时文件应保存到工作器的目录中。这样即可启用沙盒。

假设使用包含上述“工作器”特性的规则定义,除了表示输入的“srcs”特性、表示输出的“output”特性以及表示工作器启动参数的“args”特性,对 ctx.actions.run 的调用可能是:

ctx.actions.run(
  inputs=ctx.files.srcs,
  outputs=[ctx.outputs.output],
  executable=ctx.executable.worker,
  mnemonic="someMnemonic",
  execution_requirements={
    "supports-workers" : "1",
    "requires-worker-protocol" : "json"},
  arguments=ctx.attr.args + ["@flagfile"]
 )

如需查看其他示例,请参阅实现永久性工作器

示例

除了我们的集成测试中使用的示例 JSON 工作器外,Bazel 代码库还使用 Java 编译器工作器

通过传递正确的回调,您可以使用其 Scaffolding 将任何基于 Java 的工具转换为工作器。

如需查看使用工作器的规则示例,请查看 Bazel 的工作器集成测试

外部贡献者使用不同语言实现了工作器;请参阅 Bazel 持久性工作器的 Polyglot 实现。您可以在 GitHub 上找到更多示例