持久型工作器可以加快构建速度。如果构建中存在重复操作,且这些操作的启动成本较高或可以从跨操作缓存中受益,您可能需要实现自己的持久型工作器来执行这些操作。
Bazel 服务器使用 stdin/stdout 与工作器通信。它支持使用协议缓冲区或 JSON 字符串。
工作器实现包含两个部分:
创建工作器
持久型工作器需要满足以下要求:
- 它从
stdin读取 WorkRequests 。 - 它将
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_verbose 标志传递给 Bazel 会将 verbosity 字段设置为 10,但您可以手动使用较小或较大的值来获得不同数量的输出。
可选的 sandbox_dir 字段仅供支持
多路复用沙盒的工作器使用。
工作响应
WorkResponse 包含请求 ID、零或非零退出代码,以及描述在处理或执行请求时遇到的任何错误的输出消息。工作器应捕获其调用的任何工具的 stdout 和 stderr,并通过 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,
}
值为 0 的 request_id 表示“单路复用”请求,当此请求无法与其他请求并行处理时使用。服务器保证给定工作器收到的请求要么仅包含 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 的非零 request_id。服务器可能会为工作器已响应的请求发送取消请求,在这种情况下,必须忽略取消请求。
每个非取消 WorkRequest 消息都必须准确回答一次,无论是否已取消。服务器发送取消请求后,工作器可能会响应一个 WorkResponse,其中 request_id 已设置,且 was_cancelled 字段已设置为 true。发送常规 WorkResponse 也是可以接受的,但 output 和 exit_code 字段将被忽略。
为 WorkRequest 发送响应后,工作器不得触及其工作目录中的文件。服务器可以随意清理文件,包括临时文件。
创建使用工作器的规则
您还需要创建一个规则,用于生成要由工作器执行的操作。创建使用工作器的 Starlark 规则就像 创建任何其他规则一样。
此外,该规则还需要包含对工作器本身的引用,并且对其生成的操作有一些要求。
引用工作器
使用工作器的规则需要包含一个引用工作器本身的字段,因此您需要创建 \*\_binary 规则的实例来定义工作器。如果您的工作器名为 MyWorker.Java,则关联的规则可能如下所示:
java_binary(
name = "worker",
srcs = ["MyWorker.Java"],
)
这会创建“worker”标签,该标签引用工作器二进制文件。然后,您将定义一个使用工作器的规则。 此规则应定义一个引用工作器二进制文件的属性。
如果您构建的工作器二进制文件位于名为“work”的软件包中,该软件包位于构建的顶层,则属性定义可能如下所示:
"worker": attr.label(
default = Label("//work:worker"),
executable = True,
cfg = "exec",
)
cfg = "exec" 表示应构建工作器以在您的
执行平台而非目标平台上运行(即,工作器在构建期间用作
工具)。
工作操作要求
使用工作器的规则会创建供工作器执行的操作。这些操作有一些要求。
“实参”字段。此字段接受字符串列表,其中除最后一个字符串之外的所有字符串都是在启动时传递给工作器的实参。“实参”列表中的最后一个元素是
flag-file(以 @ 开头)实参。工作器会根据每个 WorkRequest 从指定的标志文件中读取实参。您的规则可以将工作器的非启动实参写入此标志文件。“执行要求”字段,其中包含一个字典,该字典包含
"supports-workers" : "1"、"supports-multiplex-workers" : "1"或两者。发送给工作器的所有操作都需要“实参”和“执行要求”字段。此外,应由 JSON 工作器执行的操作需要在
"requires-worker-protocol" : "json"执行要求字段中包含 。"requires-worker-protocol" : "proto"也是 有效的执行要求,但对于 proto 工作器来说,这不是必需的, 因为它们是默认值。您还可以在执行要求中设置
worker-key-mnemonic。如果您要为多种操作类型重复使用可执行文件,并且想要通过此工作器区分操作,这可能会很有用。在操作过程中生成的临时文件应保存到工作器的目录中。这样可以启用沙盒。
假设有一个规则定义,其中包含上述“worker”属性,以及表示输入的“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"]
)
如需查看其他示例,请参阅 实现持久型工作器。
示例
Bazel 代码库使用 Java 编译器工作器, 此外还有一个 在我们的集成测试中使用的 JSON 工作器示例 。
您可以使用其 基架 ,通过传入正确的回调,将任何基于 Java 的工具转换为 worker。
如需查看使用工作器的规则示例,请参阅 Bazel 的 工作器集成测试。
外部贡献者已使用多种语言实现了工作器;请参阅 Bazel 持久型工作器的多语言实现。 您可以在 GitHub 上找到更多示例!