十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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它告诉构建规则应该使用哪些构建工具如编译器、链接器以及如何使用规则创建者定义的参数来配置它们。工具链规则允许构建和测试规则以可预测、预先配置好的方式调用构建工具这种方式与远程执行兼容。反面的典型做法是在规则的实现函数里直接通过PATH、JAVA_HOME或其他本地环境变量来定位编译器。这些变量在远端执行环境中可能被设置为不同的值甚至完全没有被设置——PATH里指向本机/usr/bin/gcc的假设在远端容器里可能指向完全不同的编译器版本这会让构建动作变得不可复现。工具链机制正是为了消除这类对执行环境隐式状态的依赖而设计。工具链框架的关键要素完整机制参见 Toolchains其核心是把规则对具体工具的直接依赖替换为对工具链类型的抽象依赖由 Bazel 依据平台约束自动完成解析。几个必须掌握的要素toolchain_type目标代表服务于不同平台的同一类工具的抽象类型。按惯例命名为toolchain_type靠包路径区分例如//bar_tools:toolchain_type。规则声明toolchains依赖规则通过toolchains [//bar_tools:toolchain_type]声明对工具链类型的依赖默认是强制依赖也可用config_common.toolchain_type(..., mandatory False)声明可选依赖。实现函数通过ctx.toolchains[//bar_tools:toolchain_type]取得解析结果而非通过ctx.attr。工具链规则返回ToolchainInfo语言特有的*_toolchain规则不能创建任何构建动作只负责收集其他规则的产物并打包进platform_common.ToolchainInfo返回真正创建构建动作的是使用该工具链的规则。toolchain定义与注册通过toolchain()目标把语言特有工具链与toolchain_type、以及exec_compatible_with/target_compatible_with约束绑定再通过MODULE.bazel中的register_toolchains()或命令行--extra_toolchains标志注册。解析时Bazel 会过滤掉与执行平台、目标平台不兼容的工具链为每个工具链类型选出第一个兼容项并据此确定执行平台。cfg exec与cfg target的区分工具链自身的依赖中cfg exec表示构建期间要运行的工具如编译器本身需按执行平台构建cfg target表示要链接进最终产物的库需按目标平台构建。Bazel 会对工具链应用特殊的 toolchain transition强制其执行平台与父目标一致。调试解析问题时可用--toolchain_resolution_debugregex查看 Bazel 检查、跳过了哪些工具链例如bazel build //my:all --toolchain_resolution_debug.*输出全部解析信息。规则作者在远程执行场景下应优先为所用工具建立或选用工具链规则。社区目前已有 Scala、Rust、Go 等语言的工具链规则若所用工具尚无现成工具链可参照 Toolchains 中创建工具链规则一节自行实现。管理隐式依赖有状态工具导致的动作间依赖泄漏如果某个构建工具可以跨构建动作访问依赖那么这些动作在远程执行时必然失败因为每个远程构建动作都是彼此独立执行的。有些构建工具会在动作之间保留状态并访问那些没有被显式包含进工具调用中的依赖。文档给出了一个典型场景Bazel 指示某个有状态编译器先在本地构建foo编译器便保留了foo构建产物的引用随后 Bazel 指示该编译器构建依赖foo的bar但 BUILD 文件中没有把foo显式声明为编译调用的输入。只要两次动作由同一个编译器实例执行本地执行的典型情况构建就能成功而在远程执行中每个构建动作都启动一个独立的编译器实例编译器状态以及bar对foo的隐式依赖都会丢失构建随即失败。对策显式声明一切输入修复方式只有一个把动作的全部输入显式声明为依赖。Bazel 要求规则通过attr.label、attr.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 文件说明同一工具确实需要按平台分别提供实现。对策不要在源码里分发构建工具二进制除非你能确认某个构建工具二进制在你的执行平台上能安全运行否则不要把构建所需的工具二进制随源码一起分发而应二选一携带或外部引用工具源码让它在构建过程中针对远程执行平台自行编译SingleJar 即属此类其源码位于 src/java_tools/singlejar。把稳定版本的工具预先安装进远程执行环境例如装入工具链容器并通过工具链规则在构建中调用它。第二条路要求工具足够稳定、版本可控且容器内路径与配置在团队间一致——这正是工具链规则能提供的可预测、预配置调用方式。与 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_configure、python_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为保证封闭性必须指定sha256file/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_strategydocker --strategyJavacdocker --genrule_strategydocker build:docker-sandbox --experimental_docker_verbose build:docker-sandbox --experimental_enable_docker_sandbox若规则还需要额外工具可编写Dockerfile构建自定义镜像并把--experimental_docker_image的值替换为自定义镜像名。构建与常见错误执行构建bazel --bazelrc.bazelrc build --configdocker-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),仅供参考
返回列表