将 Bazel C++ 构建与系统包含目录隔离

Ker*_*ley 4 bazel

我想让我的 Bazel C++ 构建独立于/usr/include/usr/local/include等。默认情况下,如本线程中所述,编译 C++ 程序时,其中的所有文件/usr/include都可用,而不仅仅是标准文件。

实现这一目标的最佳方法是什么?

一种选择是将标准头文件放入 tarball 中并通过 HTTP 将其托管在某个位置,然后添加并new_http_repository运行.gcc-nostdinc -isysroot

Lás*_*zló 6

该解决方案由三部分组成:

  • 创建自定义 C++ 工具链定义
  • 说服 Bazel 使用该工具链
  • 说服编译器忽略其默认系统头文件并使用您的系统头文件

这是您需要的参考:

https://docs.bazel.build/versions/master/crosstool-reference.html https://docs.bazel.build/versions/master/tutorial/crosstool.html

这是一个特别好的教程:

https://github.com/bazelbuild/bazel/wiki/Yet-Another-CROSSTOOL-Writing-Tutorial

还有一些教程,我不知道它们是如何最新的:

https://github.com/bazelbuild/bazel/wiki/About-the-CROSSTOOL https://github.com/bazelbuild/bazel/wiki/Building-with-a-custom-toolchain


以下是我如何使用 GCC 在 Linux 上实现此功能:

  1. 创建一个新工作区,其中包含虚拟 cc_binary 规则。

    mkdir /tmp/bar && cd /tmp/bar
    touch WORKSPACE
    echo "cc_binary(name='x',srcs=['x.cc'])" > BUILD
    echo -e "#include <stdio.h>\nint main(void) { return 0; }" > x.cc
    
    Run Code Online (Sandbox Code Playgroud)
  2. 构建虚拟目标。

    bazel build //:x
    
    Run Code Online (Sandbox Code Playgroud)

    由于它是 cc_* 规则,Bazel 将初始化 C++ 工具链。这包括自动生成 CROSSTOOL 文件。这是一个描述编译器接口的文本文件,以便 Bazel 知道如何与其对话。

  3. 为您的自定义 crosstool 创建一个包,将自动生成的 CROSSTOOL 和 BUILD 文件复制到其中。

    mkdir /tmp/bar/my_toolchain
    touch /tmp/bar/my_toolchain/WORKSPACE
    cat $(bazel info output_base)/external/local_config_cc/CROSSTOOL > /tmp/bar/my_toolchain/CROSSTOOL
    cat $(bazel info output_base)/external/local_config_cc/BUILD > /tmp/bar/my_toolchain/BUILD
    
    Run Code Online (Sandbox Code Playgroud)

    我们将使用自动生成的文件作为模板开始。

  4. 将外部存储库规则添加到 WORKSPACE 文件。

    echo "local_repository(name='my_toolchain', path='/tmp/bar/my_toolchain')" >> WORKSPACE
    
    Run Code Online (Sandbox Code Playgroud)

    这样您就可以通过 Bazel 构建标签引用您的自定义工具链。

  5. 尝试使用您的自定义交叉工具。

    bazel build --crosstool_top=@my_toolchain//:toolchain //:x
    
    Run Code Online (Sandbox Code Playgroud)

    由于它与自动生成的相同,因此构建应该成功。

  6. 从自定义 CROSSTOOL 中的“本地”工具链编辑系统标头路径。

    注意:您可能需要编辑不同的工具链(如果要选择该工具链)。请参阅此处的工具链选择: https ://docs.bazel.build/versions/master/crosstool-reference.html#toolchain-selection

    在文本编辑器中打开/tmp/bar/my_toolchain/CROSSTOOL,找到“toolchain_identifier”为“local”的“toolchain”记录,注释掉其中的“cxx_builtin_include_directory”条目(注释字符为“#”)。

    然后为您的工具链目录添加新的“cxx_builtin_include_directory”:

    cxx_builtin_include_directory: "/tmp/bar/my_toolchain"
    
    Run Code Online (Sandbox Code Playgroud)

    您刚刚告诉 Bazel 系统标头在哪里,因此当它验证标头包含时,它会知道该路径下的标头是系统标头并且允许包含。

  7. 添加编译器标志来声明系统头的位置。

    在刚刚添加的“cxx_builtin_include_directory”行下方,添加以下内容:

    compiler_flag: "-isystem=/tmp/bar/my_toolchain"
    
    Run Code Online (Sandbox Code Playgroud)

    您刚刚告诉 Bazel 告诉编译器应该从哪里查找系统头文件。

  8. 将标头添加到编译操作的默认输入集。

    在文本编辑器中,打开/tmp/bar/my_toolchain/BUILD,找到“compiler_deps”文件组,然后编辑其“srcs”属性,例如如下所示:

    srcs = glob(["**/*.h"]),
    
    Run Code Online (Sandbox Code Playgroud)

    您刚刚告诉 Bazel 始终将这些文件包含在 C++ 编译操作中,因此构建将与沙箱和远程执行一起使用,并且如果任何系统标头(您刚刚添加到的系统标头)发生更改,Bazel 将重建 C++ 规则srcs

  9. 创建一个模拟stdc-predef.h.

    这是编译器总是希望包含的头文件。幸运的是,编译器会首先查看你的“-isystem”值,然后再回退到它自己的默认路径(我不知道你是否可以告诉它 ingore),所以我们可以像这样模拟这个文件:

    touch /tmp/bar/my_toolchain/std-predef.h
    
    Run Code Online (Sandbox Code Playgroud)
  10. 尝试使用自定义交叉工具进行构建。

    $ bazel build --crosstool_top=@my_toolchain//:toolchain //:x
    INFO: Build options have changed, discarding analysis cache.
    INFO: Analysed target //:x (0 packages loaded, 61 targets configured).
    INFO: Found 1 target...
    ERROR: /tmp/bar/BUILD:1:1: undeclared inclusion(s) in rule '//:x':
    this rule is missing dependency declarations for the following files included by 'x.cc':
      '/usr/include/stdio.h'
      (...)
    Target //:x failed to build
    Use --verbose_failures to see the command lines of failed build steps.
    INFO: Elapsed time: 0.332s, Critical Path: 0.09s, Remote (0.00% of the time): [queue: 0.00%, setup: 0.00%, process: 0.00%]
    INFO: 0 processes.
    FAILED: Build did NOT complete successfully
    
    Run Code Online (Sandbox Code Playgroud)

    由于我们的工具链目录中没有该文件stdio.h,但编译器自己的默认目录中有一个,因此它会找到该文件并抱怨缺少标头。您可以创建一个模拟stdio.h来消除这种情况。

    您可以创建一个假的stdio.h以使构建工作,或者您可以 #include <stdio.h>从 中删除该行x.cc

  11. 在自定义工具链中创建一个假系统头。

    我们将使用它来验证 Bazel 是否从自定义工具链中获取文件。

    echo 'int foo_func(int x) { return x*2; }' > /tmp/bar/my_toolchain/foo.h
    
    Run Code Online (Sandbox Code Playgroud)
  12. 重写源文件以包含自定义系统标头。

    echo -e '#include <foo.h>\nint main(int argc, char**) { return foo_func(argc); }' > x.cc
    
    Run Code Online (Sandbox Code Playgroud)
  13. 使用自定义工具链再次构建。现在一切都应该正常了。

    bazel build --crosstool_top=@my_toolchain//:toolchain //:x
    
    Run Code Online (Sandbox Code Playgroud)
  14. 尝试使用 2 个参数运行构建的二进制文件(so argc=3):

    bazel-bin/x hello world ; echo $?
    6
    
    Run Code Online (Sandbox Code Playgroud)