近一年有开始折腾 Envoy 了,在 bazel 这块踩了一些坑,总结记录下的,主要是个人的体感,不一定准确,欢迎批评指正。
bazel 是个啥
编译构建工具,跟常用的 Makefile 是类似的。
只是,bazel 更复杂,上手门槛更高,以我的个人的体验来看,主要是为了提升表达能力,可编程能力更强。
Makefile 通常是用于简单的描述,偶尔会搞个函数啥的;但是在 bazel 里,会看到大面积的自定义函数。
为啥 Envoy 需要 bazel
Envoy 是个 C++ 项目,C/C++ 这种比较老的语言,没有内置依赖包管理器这种先进特性(相比较而言,Go 语言在这块就先进了许多)。
我的理解,对于 Envoy 来说,bazel 一个主要的作用就是,补齐了依赖包管理。如果仅仅是编译,Makefile 之类的简单工具,应该也够用了。
几个概念
有几个常用的基本概念,先了解一下的
WORKSPACE
在项目的根目录,会有一个 WORKSPACE
文件,用于描述整个项目的,最主要是描述了依赖库,比较类似于 Go 语言中的 go.mod
,go.sum
。
比如 Envoy 的 WORKSPACE
文件中,有这样的描述:
1 | load("//bazel:repositories.bzl", "envoy_dependencies") |
其中,具体依赖库的详细信息,在 bazel/repository_locations.bzl
文件里。
比如下面这个示例:
1 | boringssl = dict( |
描述的是依赖库 boringssl
,是不是很像 go.mod
,go.sum
。
另外,在这里还可以频繁看到 @envoy
@envoy_api
这种,这里也表示的一个依赖库的作用域。
BUILD
BUILD 文件会有很多个,用于描述一个目标的编译过程,类似于 Makefile 中描述一个目标的构建过程。
比如,这样子的:
1 | envoy_cc_library( |
.bazelrc
这也是在项目根目录下的,用于描述 bazel 的默认配置。
比如,这个:
1 | build:linux --copt=-Wno-deprecated-declarations |
可以指定一些编译参数之类的。
执行
有了上面这些描述信息之后,最终要执行编译构建的命令就简单许多了
比如:
1 | bazel build envoy |
这里的构建目标 envoy
,来自项目根目录下的 BUILD
文件。
也可以是这样子的:
1 | bazel build //source/exe:envoy |
此时的构建目标 //source/exe:envoy
,来自项目根目录下的 source/exe/BUILD
文件了。
踩过的坑
除了上面这些一手体感,还有一些坑,也记录下的
变更编译器版本
有一次,想换个高版本的 gcc,但是修改了 PATH
后重新构建,始终不生效。
原来是,bazel 是增量编译的,所以会使用上一次编译时使用编译器,以保证整个项目是使用的同一个编译器。
所以,如果想更换编译器,需要清空下缓存:
1 | bazel clean --expunge |
split DWARF
较新版的 Envoy,启用了 -gsplit-dwarf
这个特性,也就是将调试符号放到独立的 .dwo
文件里了,以减少生成的二进制文件大小。
不过呢,这个特性还比较新,可能会有一些坑,至少在我的环境下(gdb 11
,这个版本也不低了),就有 .dwo
读取错误。
比如这样子的:
1 | DW_FORM_strp pointing outside of .debug_str section |
所以,干脆关掉这个特性,就一切正常了(主要是 bt full
这类查看局部变量的功能)。
具体操作是,注释掉 .bazelrc
中的这一行:
1 | # build:linux --features=per_object_debug_info |
最后
记录下,我这边可以完整工作的环境:
- g++ 11
- gdb 11
在这个环境下,gdb 是可以完整工作的,局部变量都可以看。
之前在一个比较老的版本上,工作是不太顺利的。