
Bazel 输出目录布局完全指南outputRoot、outputBase 与符号链接的运作原理【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel本文以 Bazel 官方的 Output Directory Layout 文档 为核心骨架系统讲解 Bazel 输出目录output directory的设计需求、当前实现的分层布局outputRoot → outputUserRoot → installBase / outputBase、bazel clean的清理语义并结合本仓库的 Java 源码与启动选项实现深入解释--output_base、--output_user_root等 startup option 的底层作用。读完本文你将能精准定位 Bazel 的每一份构建产物与缓存状态、理解bazel-bin/bazel-out等符号链接的真实指向并能在多用户、多工作区、多目标配置共存的环境下安全地管理和清理构建输出。输出目录布局的设计需求Bazel 把一切构建产物、缓存与服务器状态集中写入一个输出目录体系。之所以需要一套严谨的布局是因为构建系统必须同时满足以下要求多用户互不冲突多个用户在同一台机器上构建时输出目录不能互相覆盖多工作区并行同一时刻可以构建多个不同的工作区workspace多目标配置共存同一个工作区内可以同时为多种目标配置如k8-fastbuild与k8-opt产出结果与其他工具不冲突不能污染用户目录下其他工具的命名空间易于访问用户能方便地找到产物易于清理且支持选择性清理无歧义即使用户通过符号链接进入客户端目录输出路径的解析依然唯一一个用户的所有构建状态收敛在同一个目录下用户希望把我所有客户端的 .o 文件一次性清干净这就要求每个用户的状态整体上位于一个可整体删除的根目录之下。这 8 条需求不是凭空设想而是后续布局设计的验收标准。任何对布局的改动都必须继续满足它们。当前实现的目录分层当前实现的解决方案建立在必须从仓库内部调用 Bazel这一前提之上Bazel 必须从包含仓库边界文件repository boundary file的目录或其子目录中启动否则会直接报错。所谓仓库边界正是 仓库repository 的概念——Bazel 9 及以后基于MODULE.bazelBzlmod识别工作区边界。在此基础上输出目录被组织成如下四个层级outputRoot机器级输出根目录outputRoot是整台机器上所有 Bazel 输出的总根其默认值随平台而异平台默认 outputRootLinux~/.cache/bazelmacOSBazel 9 及更新版本~/Library/Caches/bazelWindows%HOME%若未设置则用%USERPROFILE%再退化为调用SHGetKnownFolderPath()并传入FOLDERID_Profile的结果同时存在两条环境变量覆盖规则在 Linux、macOS、Windows 上若设置了$XDG_CACHE_HOME则使用${XDG_CACHE_HOME}/bazel覆盖上述默认值若设置了$TEST_TMPDIR例如在 Bazel 自身的测试中则该值覆盖一切默认值。需要注意版本差异Bazel 8.x 及更早版本在 macOS 上使用/private/var/tmp作为 outputRoot并且忽略$XDG_CACHE_HOME。如果你在 macOS 上从 8.x 升级到 9.x输出目录会从/private/var/tmp/_bazel_$USER迁移到~/Library/Caches/bazel/_bazel_$USER旧的缓存不会自动搬移。outputUserRoot用户级目录Bazel 用户的全部构建状态位于outputRoot/_bazel_$USER这个目录被称为outputUserRoot。它是按用户隔离这一需求的直接体现——同一台机器上的不同用户各自拥有独立的_bazel_$USER子目录从结构上杜绝了互相踩踏。installBase 与 outputBase两个 MD5 命名的兄弟目录在outputUserRoot之下存在两个并列的目录都由内容哈希命名install 目录下的 installBase目录名是 Bazel 安装清单installation manifest的 MD5 哈希。它存放与 Bazel 自身二进制解包相关的内容例如主服务端 Java 应用A-server.jar、沙箱辅助二进制、进程包装器、随包附带的 JDK 与内嵌构建工具源码等。每次 Bazel 版本更新导致安装清单变化时哈希随之变化从而自然支持多个 Bazel 版本共存。outputBase目录名是工作区根目录路径的 MD5 哈希。例如在工作区根目录/home/user/src/my-project或其符号链接指向的同一目录运行 Bazel 时生成的 outputBase 为/home/user/.cache/bazel/_bazel_user/7ffd56a6e4cb724ea575aba15733d113。你可以在工作区根目录执行echo -n $(pwd) | md5sum复现这个哈希值。outputBase承载该工作区的全部状态动作缓存action cache、命令日志、外部仓库、服务端文件与execroot。它的哈希命名机制保证了同一个工作区路径永远映射到同一个 outputBase缓存可复用同一台机器上不同路径的工作区拥有互不相同的 outputBase互不干扰即使用户经由符号链接进入工作区只要解析后的真实路径一致outputBase 依然唯一——这正对应设计需求中的无歧义条款。启动选项覆盖用户可以通过两个 startup option 显式覆盖默认行为startup option 必须位于子命令左侧且选项名与值之间不能有空格参见 startup_options.txt 的说明# 覆盖 outputBase所有构建输出写入 /tmp/bazel/output bazel --output_base/tmp/bazel/output build x/y:z # 覆盖 outputUserRoot同时覆盖其下的 installBase 与 outputBase bazel --output_user_root/tmp/bazel build x/y:z从源码看这两个选项在 BlazeServerStartupOptions.java 中被定义为BAZEL_CLIENT_OPTIONS--output_base的 help 文本明确警告如果多次调用间该值不一致很可能会启动一个新的、额外的 Bazel server。因为 Bazel 为每个 output base 恰好启动一个 server典型情况下每个工作区一个 output base而使用该选项后同一个工作区可以拥有多个 output base从而在同一台机器上并发运行同一客户端的多个构建。--output_user_root则通过固定用户目录让协作用户之间可以共享构建输出——默认的_bazel_$USER按用户名隔离而显式指定后便打破了这一隔离。与之配套的还有--install_base仅测试用途、--server_jvm_out服务端 JVM 输出默认落在 output base 内等隐藏/辅助选项。完整目录布局图原文档给出了如下权威的目录树。为便于阅读此处保留其全部细节workspace-name/ 工作区根目录 bazel-my-project ..._main 指向 execRoot 的符号链接 bazel-out ...bazel-out 指向 outputPath 的便捷符号链接 bazel-bin ...bin 指向最近写入的 bin 目录 $(BINDIR) 的便捷符号链接 bazel-testlogs ...testlogs 指向测试日志目录的便捷符号链接 /home/user/.cache/bazel/ 机器上所有 Bazel 输出的根outputRoot _bazel_$USER/ 某用户专属的顶层目录取决于用户名 outputUserRoot install/ fba9a2c87ee9589d72889caf082f1029/ Bazel 安装清单的哈希installBase A-server.jar 主 Bazel server Java 应用首次运行时 从 bazel 可执行文件的数据段解包而来 linux-sandbox 沙箱辅助二进制平台相关 process-wrapper 动作执行用的进程包装器 embedded_tools/ 内含随包 JDK、构建工具源码及 server 所需的其他资源 7ffd56a6e4cb724ea575aba15733d113/ 客户端工作区根目录的哈希如 /home/user/src/my-projectoutputBase action_cache/ 动作缓存目录层级 持久化记录文件元数据时间戳 未来或许还包括 MD5 校验和 供 FilesystemValueChecker 使用 command.log 最近一次 bazel 命令的 stdout/stderr 副本 external/ 远程仓库被下载/符号链接进来的目录 server/ Bazel server 的所有相关文件 server PID、TCP 命令端口、 请求/响应 cookie、JVM 日志 jvm.out server 的调试输出 execroot/ 所有动作的工作目录。对于沙箱、 远程执行等特殊情况动作运行在 模拟 execroot 的目录中。实现细节 如目录创建位置刻意对动作隐藏。 每个动作都能相对 execroot 访问其 输入与输出 _main/ Bazel 构建的工作树与符号链接林根execRoot _bin/ 辅助工具被链接或复制到这里 bazel-out/ 构建的全部实际输出都在其下outputPath _tmp/actions/ 动作输出目录。其中包含最近一次 产生输出的 bazel 运行中每个动作的 stdout/stderr 文件 k8-fastbuild/ 每个唯一的目标 BuildConfiguration 实例 一个子目录以编码 CPU 与编译模式的 助记符命名如 k8-fastbuild、k8-opt、 k8-dbg。带 Starlark transition 的 配置会追加 ST-hash 后缀 如 k8-fastbuild-ST-abc123 bin/ Bazel 将目标配置的二进制输出到此处$(BINDIR) foo/bar/_objs/baz/ 名为 //foo/bar:baz 的 cc_* 规则的对象文件 foo/bar/baz1.o 来自源文件 //foo/bar:baz1.cc 的对象文件 other_package/other.o 来自源文件 //other_package:other.cc 的对象文件 foo/bar/baz foo/bar/baz 可能是名为 //foo/bar:baz 的 cc_binary 生成的产物 foo/bar/baz.runfiles/ //foo/bar:baz 可执行文件的 runfiles 符号链接林 MANIFEST _main/ ... testlogs/ Bazel 内部测试运行器把测试日志放到这里 foo/bartest.log 如 foo/bar.log 可能是 //foo:bartest 测试的 foo/bartest.status 输出foo/bartest.status 存放测试退出状态 如 PASSED、FAILED (Exit 1) 等 k8-opt-exec/ exec 平台对应的 BuildConfiguration 用于构建后续阶段所需的先决工具 如 Protocol Compiler packages/ 构建中引用的包如同位于普通 工作区之下图中几个层级值得单独说明execRootoutputBase/execroot/_main所有 action 的工作目录同时是符号链接林的根。Bazel 在此把工作区源码以符号链接的形式种出来让被调用的工具认为自己工作在一个普通的工作区里。outputPathexecRoot/bazel-out所有实际构建输出的总目录。其下先按目标配置configuration划分出k8-fastbuild、k8-opt、k8-dbg等子目录再在每个配置目录内按bin、testlogs等功能目录组织。配置助记符编码了 CPU 与编译模式一旦涉及 Starlark transition还会追加-ST-hash后缀以区分不同配置实例。_tmp/actions/位于bazel-out之下保存最近一次运行中每个动作的 stdout/stderr是排查动作失败的利器。*.runfiles目录的具体布局在代码中以RunfilesSupport为入口有更详细的文档说明见原文档末尾的指引。工作区内的便捷符号链接在工作区目录而不是 outputBase中Bazel 会创建bazel-workspace-name、bazel-out、bazel-testlogs、bazel-bin四个符号链接它们分别指向 outputBase 内目标配置专属目录下的对应位置。要点如下这些符号链接仅供用户方便使用Bazel 自身并不依赖它们——真正的路径解析走的是 outputBase 内部的规范路径只有当工作区根目录可写时才会创建它们bazel-bin指向的是最近写入的$(BINDIR)因为一次构建可能涉及多个目标配置bazel-bin总是指向最新产出的那一个。在源码层面这些符号链接由 SymlinkForest.java 负责种植plantSymlinkForest它同时负责把主仓库的源码以符号链接林的形式映射到execRoot下使所有包在 execroot 中如同位于普通工作区之下。设计上刻意把 execroot 内部的实现细节对 action 隐藏保证动作的可移植性与沙箱/远程执行的一致性。从源码看输出目录的封装本仓库的 Java 实现把上述概念收敛在几个核心类中BlazeServerStartupOptions.java定义install_base、output_base、output_user_root等启动选项及其帮助文本并明确每个 output base 恰好一个 server的对应关系。BlazeDirectories.java注释中直接给出了四组核心路径的口语化定义——workspace含WORKSPACE/MODULE.bazel文件、标记并配置工作区的根目录outputBase所有工作区输出构建产物 动作缓存、日志、外部仓库映射等内部文件的所在地形如_bazel_$USER/$SOME_HASH/可通过bazel info | grep output_base查询execRoot所有被调用工具的工作目录形如$OUTPUT_BASE/execroot/$WORKSPACE_IDENTIFIER可通过bazel info | grep execution_root查询outputPath构建输出写入的根路径形如$OUTPUT_BASE/execroot/$WORKSPACE_IDENTIFIER/bazel-out可通过bazel info | grep output_path查询。该类同时强调必须避免多个 Bazel 实例写入同一输出树这一约束正是通过运行中的 Bazel 实例与 output base 一一对应来强制实现的。OutputBaseInfoItem.javabazel info output_base命令背后的实现直接打印当前环境的 outputBase 路径是日常确认输出位置的最快手段。也就是说你可以用如下命令快速核对本文介绍的每个层级bazel info output_base bazel info execution_root bazel info output_path bazel info install_basebazel clean与--expunge清理语义与层级清理命令的语义与目录分层严格对应理解层级即可预测清理范围bazel clean先清空磁盘上的动作缓存action cache然后删除整个execroot目录其中包含符号链接林与全部构建输出最后移除工作区目录中的便捷符号链接bazel-bin等。执行之后outputBase 中仍保留command.log、server/、external/等状态与缓存。bazel clean --expunge在普通 clean 的基础上进一步清空整个 outputBase包括外部仓库、server 状态与命令日志等一切内容。这等价于把该工作区从这台机器的 Bazel 状态中彻底抹去下次构建将重新初始化一切。若要清理到用户级甚至机器级例如彻底释放磁盘空间则需直接操作outputUserRoot乃至outputRoot——这正是所有构建状态按用户收敛在一个目录下这一设计需求带来的便利删除_bazel_$USER即可一次性清掉该用户所有客户端的构建缓存。需要注意bazel clean不会删除installBase。因为 installBase 由安装清单哈希唯一标识且可能被同一用户的不同 server 实例共享它的生命周期由 Bazel 自身的安装管理机制如--lock_install_base防并发回收负责而不是由工作区级清理命令管辖。实战要点小结定位日常使用bazel info系列命令output_base、execution_root、output_path、install_base即可精确获得各层路径无需手动推算 MD5。覆盖CI 或需要共享产物时用--output_base/--output_user_root指定固定位置但务必牢记--output_base变化会派生新的 Bazel server频繁切换会白白增加 server 数量。清理增量产物残留用bazel clean需要连外部仓库与 server 状态一起重来例如排查非封闭行为时用bazel clean --expunge要清空某用户全部状态则删除整个_bazel_$USER目录。不要依赖符号链接bazel-bin、bazel-out仅是便捷入口且指向最近写入的配置目录脚本中对产物路径的引用应以bazel info的规范路径为准。版本差异macOS 上 Bazel 8.x 与 9.x 的 outputRoot 不同/private/var/tmpvs~/Library/Caches/bazel升级后旧缓存不会自动迁移。【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考