Bazel 构建输出

Bazel 使用一种很有主见的方案来存储构建工件, 常常会给开发者造成混淆本页将介绍 工作。

Bazel output_base

根据设计,bazel build 命令绝不会向项目的源写入文件 目录(或其某个子目录)。Bazel 会使用特定于用户的 Parallel 目录来存储所有输出(称为 user_output_root), 的默认值为:

  • ~/.cache/bazel/_bazel_$USER(在 Linux 上)。
  • %HOME%\_bazel_%USERNAME%(在 Windows 上)。

对于运行 bazel 的每个工作区目录,都会有一个名为 output_base 是在 user_output_root 下创建的,如下所示:

  ${user_output_root}/<WORKSPACE_HASH>`

其中 <WORKSPACE_HASH> 是一个长十六进制哈希(根据 目录的绝对路径)。

由于此路径完全不可预测,因此命令 在 Bazel 项目中使用时,bazel info output_base 将输出此消息。 例如:

$ mkdir -p /tmp/project1 && cd /tmp/project1 && touch WORKSPACE.bazel
$ bazel info output_base
Starting local Bazel server and connecting to it...
/usr/local/google/home/digit/.cache/bazel/_bazel_digit/6c7b78994da78136b5cb6b7607361ad3

$ mkdir -p /tmp/project2 && cd /tmp/project2 && touch WORKSPACE.bazel
$ bazel info output_base
Starting local Bazel server and connecting to it...
/usr/local/google/home/digit/.cache/bazel/_bazel_digit/c37b9d68308ee5abe2f781dd38b733b9

$ mkdir -p /tmp/not-a-project && cd /tmp/not-a-project
$ bazel info output_base
WARNING: Invoking Bazel in batch mode since it is not invoked from within a workspace (below a directory having a WORKSPACE file).
ERROR: The 'info' command is only supported from within a workspace (below a directory having a WORKSPACE file).
See documentation at https://bazel.build/concepts/build-ref#workspace

此架构非常灵活,但并不完美:

  • Pro:同一台计算机上的多个用户可以共享同一项只读文件 项目目录中。

  • Pro:同一用户的多个项目目录将始终使用 独立的输出路径。

  • Con:可直接从命令行查看生成的文件,甚至可以 图形探索器很困难

  • Con:移除项目目录(例如包含 rm -rf .../my-project) 不会移除其输出(造成严重浪费)。

  • Con:移动项目目录(例如使用 mv my-project my-project2) 不会重复使用之前的 output_base 内容(并将旧内容保留 ,现在无法访问)。

  • Conuser_output_root 的默认位置,因此 output_base 通常与 项目。这可能会对性能 / 磁盘造成意想不到的后果 。

调用 bazel clean 即可从当前 output_base 中移除构建输出。 此操作必须在移除源项目目录之前完成。

在实践中,使用user_output_root 从未适当清理的过时 Bazel 项目构建工件。 更糟糕的是,尝试直接手动移除 user_output_root 可能不起作用, 因为默认情况下,Bazel 会创建只读的构建制品,这会阻止 运行命令(例如 rm -rf ~/.cache/bazel)!

Bazel output_base 内容:

实际上,以下内容存储在 output_base 下:

  • 外部代码库的工作区目录

    它们对应于外部项目依赖项。这些通常不是 项目的源代码树,但是从网络上下载或生成的 以编程方式

    其内容存储在 ${output_base}/external/<repository_name> 下, 其中 external 部分经过硬编码,<repository_name> 用于匹配 外部代码库的规范名称。

  • 构建工件

    运行 bazel build 生成的文件。这些文件存储在以下位置:

    ${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/bin/
    

    其中:

    • execrootbazel-outbin 部分是硬编码的, 已更改。

    • 对于在项目自己的 BUILD.bazel 文件中定义的目标, <workspace_name> 默认为 __main__,除非是在项目的 WORKSPACE.bazel 文件,其中包含以下指令:

      workspace(
        name = "my_project",
      )
      ```
    
    - For targets defined in external repositories, `<workspace_name>` matches
    the repository's canonical name.
    
    - The `<config_dir>` value is a name derived from the build configuration used
    to configure the target that generated the build artifact. This allows
    rebuilding the same target in different ways, each time using a different
    `<config_dir>` value.
    
    Note: The `<config_dir>` value is **generally unpredictable**. More on this [here][bazel-config-dirs]
    
  • 测试结果

    调用 bazel test 时生成的日志文件,存储在 ${output_base}/execroot/<workspace_name>/bazel-out/<config_dir>/testlogs/

  • 内部缓存和配置文件

    供远程构建和远程缓存功能使用。要忽略这类文件, 开发者。

Bazel execroot 目录:

execroot 用于运行生成 build 工件、 但具体如何实现取决于特定操作是否启用了沙盒。

  • 在 Linux 上,默认情况下,系统会为所有操作启用沙盒。 Windows 不支持沙盒(从 Bazel 7 开始)。

  • Bazel 操作可以有意停用沙盒,只需使用 定义中包含 no-sandbox 标记。

  • 您可以通过如下选项全局停用沙盒: 在调用 bazel 时返回 --spawn_strategy=local

未采用沙盒机制时:

停用沙盒后,为以下项目生成工件的所有构建操作: 指定的工作区会将输出文件放在 ${output_base}/execroot/<workspace_name>

该操作的 因此将是相对于该节点的

Bazel 可确保命令所用输入源的符号链接 在 execroot 下创建。

例如,要编译 //src/foo/foo.cc 文件的操作, (包含与 //src/foo/foo.h 对应的 #include "foo.h")可以 如下所示:

gcc -c -o bazel-out/k8-fastbuild/bin/src/foo/foo.o src/foo/foo.cc -Isrc/foo

这样做的原因是:

  • 在运行该命令之前,Bazel 会创建符号链接, 指向 $PROJECT/src${output_base}/execroot/__main__/src, 以使 src/foo/foo.ccsrc/foo/foo.h 解析为 $PROJECT/src/foo/foo.cc$PROJECT/src/foo/foo.h

  • 该位置 bazel-out/k8-fastbuild/bin/src/foo/foo.o 是最后一个位置 输出路径(适用于通过在foo.cc 此命令使用的 build 配置。

使用沙盒时:

启用沙盒后,Bazel 会为每个命令创建一个临时 目录(例如 ${output_base}/sandbox/linux-sandbox/<random-number>) 并创建一个符号链接树来模拟其下的 execroot 布局,但是 只针对它知道的输入。在这种情况下,如下所示:

  • 输入 ${sandbox}/execroot/__main__/src/foo/foo.cc 的符号链接 和 ${sandbox}/execroot/__main__/src/foo/foo.h 指向 分别为 $PROJECT/src/foo/foo.cc$PROJECT/src/foo/foo.h

  • ${sandbox}/execroot/__main__ 下运行相同的命令。 而非 ${output_base}/execroot/__main__

  • 命令完成后,从位于 ${sandbox}/execroot/__main__/bazel-out/k8-fastbuild/bin/src/foo/foo.o 到达最终地点: ${output_base}/execroot/__main__/bazel-out/k8-fastbuild/bin/src/foo/foo.o

  • 最后是沙盒目录及其所有内容。这也 表示将忽略未声明的输出。