news 2026/9/12 19:43:26

Bazel 远程执行环境下的自定义规则适配指南:工具链、隐式依赖与 WORKSPACE 封闭性改造

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Bazel 远程执行环境下的自定义规则适配指南:工具链、隐式依赖与 WORKSPACE 封闭性改造

Bazel 远程执行环境下的自定义规则适配指南:工具链、隐式依赖与 WORKSPACE 封闭性改造

【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel

远程执行(remote execution)允许 Bazel 把构建动作分发到数据中心等独立平台上执行,但这要求构建和测试规则满足一套与本地执行截然不同的约束:动作必须相互隔离、环境必须可复现。本文以 Bazel 官方文档 Adapting Bazel Rules for Remote Execution 为骨架,面向编写自定义构建与测试规则的 Bazel 用户,系统讲解通过工具链规则调用构建工具、管理隐式依赖、处理平台相关二进制以及改造 configure 风格 WORKSPACE 规则四大适配主题,并给出 Docker sandbox 与 workspacelog 两种排障手段。读完本文,你将能够对照检查清单诊断自己的规则在远程执行下的失败点,并动手完成规则层面的远程执行适配。

远程执行的基本前提:三种平台与两类约束

三种平台术语

要理解远程执行对规则的额外要求,首先需要统一平台术语。参照 Platforms 中 Bazel 对平台角色的划分,远程执行场景下同一构建涉及三种平台:

  • Host platform(主机平台):Bazel 自身运行所在的平台,即开发者的机器或 CI 服务器。
  • Execution platform(执行平台):Bazel 的构建动作实际运行所在的平台。本地构建时它等于主机平台;远程执行时它是远端数据中心里的机器或工具链容器。
  • Target platform(目标平台):构建产物(以及部分动作)最终要运行于其上的平台。

一次构建只有一个主机平台,但通常可以有多个执行平台和目标平台。远程执行的困难正源于执行平台不再与主机平台重合,且一个构建中的不同动作可能被分发到不同的执行平台上。

两类硬性约束

配置远程执行构建时,必须遵循本页所述准则才能保证构建在远端无错误执行,根源在于远程执行的两点本质:

  • 隔离的构建动作(Isolated build actions):构建工具不保留跨动作的状态,依赖不能在动作之间泄漏。每个远程动作都像在一台全新机器上运行。
  • 多样的执行环境(Diverse execution environments):本地的构建配置(本机安装的工具、环境变量、绝对路径)并不总是适用于远程执行环境。

下面四个主题——工具链调用、隐式依赖、平台相关二进制、configure 风格 WORKSPACE 规则——正是这两类约束在自定义规则上最常见的落点。

通过 toolchain 规则调用构建工具

为什么不能用 PATH、JAVA_HOME 直接调用工具

Bazel 工具链规则(toolchain rule)是一种配置提供者(configuration provider),它告诉构建规则应该使用哪些构建工具(如编译器、链接器),以及如何使用规则创建者定义的参数来配置它们。工具链规则允许构建和测试规则以可预测、预先配置好的方式调用构建工具,这种方式与远程执行兼容。

反面的典型做法是:在规则的实现函数里直接通过PATHJAVA_HOME或其他本地环境变量来定位编译器。这些变量在远端执行环境中可能被设置为不同的值,甚至完全没有被设置——PATH里指向本机/usr/bin/gcc的假设在远端容器里可能指向完全不同的编译器版本,这会让构建动作变得不可复现。工具链机制正是为了消除这类对执行环境隐式状态的依赖而设计。

工具链框架的关键要素

完整机制参见 Toolchains,其核心是把"规则对具体工具的直接依赖"替换为"对工具链类型的抽象依赖",由 Bazel 依据平台约束自动完成解析。几个必须掌握的要素:

  1. toolchain_type目标:代表"服务于不同平台的同一类工具"的抽象类型。按惯例命名为toolchain_type,靠包路径区分,例如//bar_tools:toolchain_type
  2. 规则声明toolchains依赖:规则通过toolchains = ["//bar_tools:toolchain_type"]声明对工具链类型的依赖(默认是强制依赖;也可用config_common.toolchain_type(..., mandatory = False)声明可选依赖)。实现函数通过ctx.toolchains["//bar_tools:toolchain_type"]取得解析结果,而非通过ctx.attr
  3. 工具链规则返回ToolchainInfo:语言特有的*_toolchain规则不能创建任何构建动作,只负责收集其他规则的产物并打包进platform_common.ToolchainInfo返回;真正创建构建动作的是使用该工具链的规则。
  4. toolchain定义与注册:通过toolchain()目标把语言特有工具链与toolchain_type、以及exec_compatible_with/target_compatible_with约束绑定,再通过MODULE.bazel中的register_toolchains()(或命令行--extra_toolchains标志)注册。解析时,Bazel 会过滤掉与执行平台、目标平台不兼容的工具链,为每个工具链类型选出第一个兼容项,并据此确定执行平台。
  5. cfg = "exec"cfg = "target"的区分:工具链自身的依赖中,cfg = "exec"表示"构建期间要运行的工具"(如编译器本身,需按执行平台构建),cfg = "target"表示"要链接进最终产物的库"(需按目标平台构建)。Bazel 会对工具链应用特殊的 toolchain transition,强制其执行平台与父目标一致。
  6. 调试:解析问题时可用--toolchain_resolution_debug=regex查看 Bazel 检查、跳过了哪些工具链,例如bazel build //my:all --toolchain_resolution_debug=.*输出全部解析信息。

规则作者在远程执行场景下应优先为所用工具建立或选用工具链规则。社区目前已有 Scala、Rust、Go 等语言的工具链规则,若所用工具尚无现成工具链,可参照 Toolchains 中"创建工具链规则"一节自行实现。

管理隐式依赖

有状态工具导致的动作间依赖泄漏

如果某个构建工具可以跨构建动作访问依赖,那么这些动作在远程执行时必然失败,因为每个远程构建动作都是彼此独立执行的。有些构建工具会在动作之间保留状态,并访问那些没有被显式包含进工具调用中的依赖。

文档给出了一个典型场景:Bazel 指示某个有状态编译器先在本地构建foo,编译器便保留了foo构建产物的引用;随后 Bazel 指示该编译器构建依赖foobar,但 BUILD 文件中没有把foo显式声明为编译调用的输入。只要两次动作由同一个编译器实例执行(本地执行的典型情况),构建就能成功;而在远程执行中,每个构建动作都启动一个独立的编译器实例,编译器状态以及barfoo的隐式依赖都会丢失,构建随即失败。

对策:显式声明一切输入

修复方式只有一个:把动作的全部输入显式声明为依赖。Bazel 要求规则通过attr.labelattr.label_list等属性把源文件、库、数据文件与工具全部纳入目标的依赖图,这样这些文件才会被作为输入参数(而非靠编译器内部状态或本机文件系统)传给执行动作。Bazel 对依赖的定义与要求可参见 Dependencies。凡是在规则实现中通过字符串拼接的路径、环境变量、ctx.execute探测出来的文件,都应当重新审视并转换为显式声明的标签依赖。

用 Docker sandbox 提前暴露问题

自 Bazel 0.14.1 起,本地 Docker sandbox 具备与远程执行完全相同的依赖限制:每个动作都在全新的容器中执行,只有显式声明的输入输出能跨越容器边界。因此可以先用它在本机构建来识别并解决依赖相关的构建错误,而不必等待接入真实的远程执行服务。具体做法见下文"用 Docker sandbox 提前排障"一节及 Troubleshooting Bazel Remote Execution with Docker Sandbox。

管理平台相关二进制

问题:主机平台二进制无法在任意执行平台运行

通常,在主机平台上构建出来的二进制不能安全地在任意远程执行平台上运行,因为两者可能依赖的工具链库不匹配。文档举了 Bazel 自带的SingleJar工具为例:随 Bazel 分发的 SingleJar 面向主机平台构建;而在远程执行场景下,SingleJar 必须作为你构建代码过程的一部分重新编译,使其面向远程执行平台。这一点在仓库的 tools/jdk/BUILD 中可见一斑——该文件通过alias结合select在不同平台条件下(如//src/conditions:darwin_x86_64)选择不同的预构建 BUILD 文件,说明同一工具确实需要按平台分别提供实现。

对策:不要在源码里分发构建工具二进制

除非你能确认某个构建工具二进制在你的执行平台上能安全运行,否则不要把构建所需的工具二进制随源码一起分发,而应二选一:

  1. 携带或外部引用工具源码,让它在构建过程中针对远程执行平台自行编译(SingleJar 即属此类,其源码位于 src/java_tools/singlejar)。
  2. 把稳定版本的工具预先安装进远程执行环境(例如装入工具链容器),并通过工具链规则在构建中调用它。

第二条路要求工具足够稳定、版本可控,且容器内路径与配置在团队间一致——这正是工具链规则能提供的"可预测、预配置"调用方式。

与 runfiles 的关联

平台相关二进制的另一个常见陷阱是把工具或库的绝对路径硬编码进规则。正确做法是让这些文件经由标准构建动作进入 Bazel 的runfiles树,再由动作通过 runfiles 库定位。仓库 Runfiles 给出了 C++、Go、Python、Shell 四种语言的 runfiles 库用法示例,对应的可直接运行样例见 examples/cpp/runfile.cc、examples/go/runfile.go、examples/py/runfile.py、examples/shell/runfile.sh。硬编码路径在远端会直接失效,而 runfiles 定位不依赖主机文件系统布局,天然兼容远程执行。

管理 configure 风格 WORKSPACE 规则

四类与远程执行不兼容的操作

Bazel 的WORKSPACE规则(及 Bzlmod 下的仓库规则,见 Repository Rules)常用于探测主机平台上的工具与库。本地构建时主机平台就是执行平台,探测结果直接可用;但远程执行时,如果构建显式依赖本地构建工具和产物,而远程执行平台与主机平台不一致,构建就会失败。以下四类WORKSPACE规则操作与远程执行不兼容:

  • 构建二进制:在WORKSPACE规则中执行编译动作,产出的二进制面向主机平台;当执行平台不同于主机平台时,这些二进制与远程执行平台不兼容。
  • 安装pip:通过WORKSPACE规则安装的pip包要求其依赖已预装在主机平台上;这类针对主机平台构建的包,在异于主机平台的执行平台上同样不兼容。
  • 符号链接到本地工具或产物:通过WORKSPACE规则创建的指向主机平台已安装工具或库的符号链接,会让远程执行平台上的构建失败,因为 Bazel 在远端找不到这些目标。正确的替代方案是:用标准构建动作创建符号链接,使被链接的工具和库位于 Bazel 的runfiles树内可被访问;切勿用repository_ctx.symlink把目标文件链接到外部仓库目录之外。
  • 变更主机平台:避免在 Bazelrunfiles树之外创建文件、创建环境变量等操作,这些在远程执行平台上可能表现异常。

拆分原则:哪些留在 WORKSPACE,哪些移入构建规则

如果某个外部依赖执行了依赖主机平台的特定操作,应按如下方式在WORKSPACE与构建规则之间拆分:

  • 平台检查与依赖枚举:这些操作适合留在WORKSPACE规则中本地执行——检查安装了哪些库、下载需要构建的包、准备编译所需的产物。但为了远程执行,这些规则还必须支持使用**预检查产物(pre-checked artifacts)**来提供原本要通过主机平台检查才能获得的信息,让 Bazel 能把这些依赖描述得如同本地依赖一样。实现手段是条件语句或--override_repository标志。
  • 生成或编译目标特定产物、变更平台:这些操作必须交给常规构建规则,在执行平台(远端)上执行。为外部依赖产出目标特定产物的动作必须在构建过程中执行。

预检查产物的工作方式

要更轻松地为远程执行生成预检查产物,可以用WORKSPACE规则输出生成文件:在每个新的执行环境中(例如在每个工具链容器内)运行这些规则,再把远程执行构建的输出检查进你的源码仓库供后续引用。

文档中的典型案例(如 TensorFlow 的cuda_configurepython_configure类规则)展示了该模式:WORKSPACE规则先探测主机环境并生成对应的 BUILD 文件;本地执行时使用探测主机环境得到的文件;远程执行时,通过基于环境变量的条件语句让规则改用检查进仓库的文件。这些 BUILD 文件里声明的genrule既能在本地也能在远端运行,把以前靠repository_ctx.symlink完成的处理工作改由常规构建动作完成。

用 workspace log 定位非封闭行为

WORKSPACE规则中的任意处理都在主机本地执行,是潜在的不可封闭(non-hermetic)来源,通常经由repository_ctx与主机交互引入。自 Bazel 0.18 起,可以给 Bazel 命令添加标志--experimental_workspace_rules_log_file=[PATH]获得部分潜在非封闭操作的日志,详细用法见 Finding Non-Hermetic Behavior in WORKSPACE Rules。要点:

  • 日志按实际执行顺序记录事件,被缓存的步骤不会出现在日志里,因此排查前先运行bazel clean --expunge保证所有初始化都会重跑。
  • 函数可能被重新执行,相关事件会多次出现在日志中;目前仅记录 Starlark 事件。
  • 日志是WorkspaceEvent消息的二进制 proto 流,需要用仓库中的解析器转换为文本:bazel build src/tools/workspacelog:parser后运行bazel-bin/src/tools/workspacelog/parser --log_path=/tmp/workspacelog > /tmp/workspacelog.txt,可用--exclude_rule "//external:local_config_cc"过滤内建规则噪声(可重复指定多次)。

仓库 src/tools/workspacelog 中 WorkspaceLogParser.java 的实现印证了上述流程:ExcludingLogParser逐条parseDelimitedFrom读取WorkspaceEvent,跳过excludedRules集合中命中的规则上下文,其余事件以print输出并按分隔符排版。

日志中被标记为潜在非封闭的动作及其审查要点:

动作审查要点
execute在主机环境执行任意命令,检查是否引入了对主机环境的依赖
download/download_and_extract为保证封闭性,必须指定sha256
file/template本身不算非封闭,但可能是把主机依赖引入仓库的机制,需确认输入来源不依赖主机环境
os本身不算非封闭,但最容易引入主机依赖;封闭构建一般不应调用它(它运行在主机而非远端 worker 上)
symlink通常安全,但指向仓库外部或绝对路径的符号链接会在远端 worker 上出问题;基于主机属性创建的符号链接同样可疑
which探测主机安装的程序通常有问题,因为远端 worker 的配置可能不同

用 Docker sandbox 提前排障

原理与三种限制

本地构建成功而远程执行失败的案例,其最常见的成因都收录在本文所述规则适配问题中。Docker sandbox 通过在本地复刻远程执行的三类限制来帮助排查:

  • 构建动作在工具链容器中执行:可用同一套工具链容器在本地和远程(支持容器化远程执行的服务)运行构建。
  • 没有多余数据跨越容器边界:只有显式声明的输入输出能在构建动作成功完成后进出容器。
  • 每个动作都在全新容器中执行:每次 spawn 的构建动作对应一个全新、唯一的容器。

注意:启用 Docker sandbox 后构建耗时显著增加,属正常现象。

前置条件与 .bazelrc 配置

  • 安装 Docker 并配置好运行权限。
  • 安装 Bazel 0.14.1 或更高版本(更早版本不支持该特性)。
  • .bazelrc中按docker-sandbox配置添加标志,参考示例:
# Docker Sandbox Mode build:docker-sandbox --host_javabase=<...> build:docker-sandbox --javabase=<...> build:docker-sandbox --crosstool_top=<...> build:docker-sandbox --experimental_docker_image=<...> build:docker-sandbox --spawn_strategy=docker --strategy=Javac=docker --genrule_strategy=docker build:docker-sandbox --experimental_docker_verbose build:docker-sandbox --experimental_enable_docker_sandbox

若规则还需要额外工具,可编写Dockerfile构建自定义镜像,并把--experimental_docker_image的值替换为自定义镜像名。

构建与常见错误

  • 执行构建:bazel --bazelrc=.bazelrc build --config=docker-sandbox <target>,构建可能比平时慢至多四倍。
  • 若报错ERROR: 'docker' is an invalid value for docker spawn strategy.,用--experimental_docker_verbose开启详细报错;该错误通常源于 Docker 安装有问题或当前用户缺少执行权限。

常见失败模式与对策速查

  • runfiles 树引用的文件、工具、二进制或资源缺失:确认受影响目标的所有依赖都已按 Dependencies 显式声明,即"管理隐式依赖"一节所述问题。
  • 绝对路径或PATH变量引用的文件缺失:确认工具已装入工具链容器,并用工具链规则声明指向缺失资源的依赖(即"通过 toolchain 规则调用构建工具"一节所述问题)。
  • 二进制执行失败:某个构建规则引用了与执行环境(Docker 容器)不兼容的二进制,即"管理平台相关二进制"一节所述问题。
  • @local-jdk的文件缺失或报错:本机 Java 二进制泄漏进了构建且与其不兼容,应在规则和目标中使用java_toolchain而非@local_jdk
  • 构建在加载或分析阶段失败WORKSPACE中声明的规则存在与远程执行不兼容的操作,按"管理 configure 风格 WORKSPACE 规则"一节的成因与对策处理。

容器内排障(可选进阶)

还可在 Docker 容器内运行 Bazel 本身,把构建与构建动作的执行分离:以debian:stretch为基础镜像构建bazel_container,挂载 docker socket 与/tmp(供 Bazel spawn 子容器及共享文件),用--output_user_root=/tmp/bazel_docker_root启动。该方法刻意使用与工具链容器不兼容的基础镜像,任何从本地环境泄漏进工具链容器的二进制都会导致构建错误,从而暴露隐蔽的本地依赖。该方式目前是实验性的,未获官方支持。完整步骤见 Troubleshooting Bazel Remote Execution with Docker Sandbox。

适配检查清单

检查项对应主题验证手段
规则通过工具链规则调用工具,而非PATH/JAVA_HOME/绝对路径工具链调用--toolchain_resolution_debug=.*观察解析结果
动作的全部输入已在 BUILD 文件中显式声明,无有状态工具依赖泄漏隐式依赖Docker sandbox 构建
构建工具二进制随源码分发前确认可在执行平台运行;优先源码构建或装入工具链容器平台相关二进制Docker sandbox 构建 + runfiles 定位
WORKSPACE规则仅做平台检查与依赖枚举;产物生成与平台变更交给构建规则configure 风格 WORKSPACE--experimental_workspace_rules_log_file+ workspacelog 解析
不用repository_ctx.symlink链接外部目录文件;符号链接经标准构建动作进入 runfilesconfigure 风格 WORKSPACEworkspacelog 审查symlink事件
下载类仓库规则均指定sha256封闭性workspacelog 审查download事件

完成上述改造后,可先以 Docker sandbox 本地验证,再接入真实的远程执行服务(协议为 open-source 的 gRPC remote execution API,可参考 Remote Execution Overview 中的服务选型)做最终确认。规则层面的封闭性、平台无关性与显式依赖声明,是 Bazel 远程执行得以稳定运行的基石。

【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 19:43:13

私有化部署ShareLaTeX:构建高效中文LaTeX协作平台

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 19:42:24

Intel TSX如何被利用破解KASLR安全防护

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 19:42:05

Python处理扫描PDF底色发黄问题的技术方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 19:41:27

基于Django的民宿预订系统设计与实现

1. 项目概述&#xff1a;基于Django的民宿预订系统最近在整理毕业设计资料时&#xff0c;翻到了当年做的民宿预订系统项目。这个用Django框架开发的系统虽然算不上复杂&#xff0c;但完整实现了民宿行业的在线预订全流程。现在回头看&#xff0c;这个项目确实涵盖了Web开发的多…

作者头像 李华
网站建设 2026/9/12 19:40:16

【java】数组的定义和使用

数组的基本概念数组创建T[] 数组名 new T[N];T&#xff1a;数组中存放元素的数据类型T [] &#xff1a;代表数组本身的类型N&#xff1a;数组的长度&#xff08;数组能存放多少个元素&#xff09;int[] arr1new int[10];double[] array2new double[10];String[] array3new Str…

作者头像 李华