持久性工作器可以加快构建速度。如果您的 build 中有重复操作,启动成本较高或可以从跨操作缓存中受益,您可能需要实现自己的持久性工作器来执行这些操作。
Bazel 服务器使用 stdin
/stdout
与工作器进行通信。它支持使用协议缓冲区或 JSON 字符串。
工作器实现包含两个部分:
创建工作器
持久性工作器需要满足一些要求:
- 它会从其
stdin
读取 WorkRequests。 - 它会将 WorkResponses(仅
WorkResponse
)写入其stdout
。 - 它接受
--persistent_worker
标志。封装容器必须识别--persistent_worker
命令行标志,并且仅在传递该标志时使自身保持持久性,否则它必须进行一次性编译并退出。
如果您的程序遵循这些要求,则可用作持久性工作器!
工作请求
WorkRequest
包含工作器的参数列表、表示工作器可以访问的输入的路径摘要对(不强制执行,但您可以使用此信息进行缓存)以及请求 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 时,表示“singleplex”请求,在此请求无法与其他请求并行处理时使用。服务器保证给定的工作器仅接收 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
,但会忽略 output
和 exit_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"
表示应将工作器构建在执行平台(而不是目标平台)上运行(即,在构建过程中将工作器用作工具)。
工作操作要求
使用工作器的规则会创建可供工作器执行的操作。这些操作有几项要求。
“参数”字段。这需要接受一个字符串列表,除最后一个字符串之外,其余的都是在启动时传递给工作器的参数。“参数”列表中的最后一个元素是
flag-file
(@ 前置)参数。工作器会根据每个 WorkRequest 从指定的 flagfile 中读取参数。您的规则可以将工作器的非启动参数写入此标志文件。“执行要求”字段,它接受包含
"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 工作器。
通过传递正确的回调,您可以使用其 Scaffolding 将任何基于 Java 的工具转换为工作器。
如需查看使用工作器的规则示例,请查看 Bazel 的工作器集成测试。
外部贡献者使用多种语言实现了工作器;请参阅 Bazel 持久性工作器的 Polyglot 实现。您可以在 GitHub 上找到更多示例!