创建永久性工作器

报告问题 查看源代码 每夜 build · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

worker 实现包含两个部分:

创建工作器

持久性工作器需要满足以下要求:

  • 它会从 stdin 读取 WorkRequest
  • 它会将 WorkResponses(且仅 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 获取额外的调试输出。工作器完全可以自行决定要输出什么以及如何输出。值越高,表示输出越详细。将 --worker_verbose 标志传递给 Bazel 会将 verbosity 字段设置为 10,但您可以针对不同的输出手动使用更小或更大的值。

可选的 sandbox_dir 字段仅供支持多重沙盒的 worker 使用。

工作回复

WorkResponse 包含请求 ID、零或非零退出代码,以及用于说明处理或执行请求时遇到的任何错误的输出消息。worker 应捕获其调用的任何工具的 stdoutstderr,并通过 WorkResponse 报告这些信息。将其写入工作器进程的 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 或仅为大于 0 的值。系统会按顺序发送 Singleplex 请求,例如,如果服务器在收到响应之前未发送其他请求(取消请求除外,请参阅下文)。

备注

  • 每个协议缓冲区前面都带有其长度(采用 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;对于多路复用工作器,request_id 字段将是之前发送的 WorkRequest 的非 0。服务器可能会针对 worker 已响应的请求发送取消请求,在这种情况下,必须忽略取消请求。

每条非取消 WorkRequest 消息都必须恰好响应一次,无论是否已取消。服务器发送取消请求后,工作器可能会响应 WorkResponse,并将 request_id 设置为 true 并将 was_cancelled 字段设置为 true。您也可以发送常规的 WorkResponse,但系统会忽略 outputexit_code 字段。

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

制定使用工作器的规则

您还需要创建一个规则,用于生成由 worker 执行的操作。创建使用 worker 的 Starlark 规则与创建任何其他规则一样。

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

提及 worker

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

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

这会创建“worker”标签,该标签是指工作器二进制文件。然后,您将定义一项使用该 worker 的规则。此规则应定义一个引用工作器二进制文件的属性。

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

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

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

工作操作要求

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

  • “arguments” 字段。该函数需要一个字符串列表,但最后一个字符串除外,其所有字符串均为在启动时传递给 worker 的参数。“arguments”列表中的最后一个元素是 flag-file(带有 @ 前缀)参数。工作器会按 WorkRequest 读取指定标志文件中的参数。您的规则可以将工作器的非启动参数写入此标志文件。

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

    发送给工作器的所有操作都必须包含“arguments”和“execution-requirements”字段。此外,应由 JSON Worker 执行的操作需要在“执行要求”字段中包含 "requires-worker-protocol" : "json""requires-worker-protocol" : "proto" 也是有效的执行要求,但对于 proto 工作器而言,这不是必需的,因为它们是默认的。

    您还可以在执行要求中设置 worker-key-mnemonic。如果您要针对多种操作类型重复使用可执行文件,并且希望通过此 worker 区分操作,这可能会很有用。

  • 操作过程中生成的临时文件应保存到 worker 的目录中。这会启用沙盒功能。

假设规则定义包含上述“worker”属性,除了表示输入的“srcs”属性、表示输出的“output”属性和表示 worker 启动参数的“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 编译器工作器

您可以使用他们的脚手架,通过传入正确的回调将任何基于 Java 的工具转换为工作器。

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

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