bazel C++语法入门
Bazel是Google开源的一款代码构建工具。Bazel支持多种语言并且跨平台,还支持增量编译、自动化测试和部署、具有再现性(Reproducibility)和规模化等特征。Bazel 在谷歌大规模软件开发实践能力方面起着至关重要的作用。
Install Bazel
macOS: brew install bazel
Ubuntu: sudo apt install bazel
bazel的所有代码都在当前工程,每个工程都是一个 WORKSPACE 。
每个WORKSPACE下有多个BUILD文件。
BUILD内是多个targets,这些targets都是以starlark语言声明。
Starlark语言
- 和python很像。
- 线程安全
- 数据类型有 None, bool, dict, function, int, list, string, depset, struct
- 可变数据结构有 lists 和 dicts
命令行
规则
1 | bazel [<startup options>] <command> [<args>] |
或
1 | bazel [<startup options>] <command> [<args>] -- [<target patterns>] |
工作原理
- 加载与target有关的BUILD文件
- 分析inputs和dependencies,生成 action graph
- 执行graph,产出outputs
action graph: bazel依赖这个图来追踪文件变化,以及是否需要重新编译,并且还可以为用户提供代码之间的依赖关系图。
依赖声明
- 同一个文件BUILD之内
1 | cc_library( |
- 不同BUILD文件之间
1 | cc_library( |
:hello-time 定义在 lib 目录
不同的目录BUILD在bazel中被称为 不同的package
- 可见性
同一个package内的targets默认互相可见
不同package之间targets的可见性需要手动定义
1 | cc_library( |
可以在每个package的BUILD文件顶部声明其中的targets对其他包的默认可见性
1 | package( |
对所有包可见声明如下
1 | cc_proto_library( |
target
有2种
- rule target,比如 cc_library
- file target
C++ 最佳实践
BUILD文件
- 一个包(package)包含一个BUILD文件
- 每个BUILD文件包含一个 cc_library 规则目标, 每条规则都是一个目标(target)
- 尽可能细粒度C++库,以提高并行速度,并减少增量编译
- 如果srcs中只有一个文件,那么 cc_library的名字和这个文件名相同,比如:
1 | cc_library( |
- 每个library都有个单独的test target,并且以_test结尾命名这个target和测试文件名,比如
1 | cc_test( |
include路径
- 所有include路径都相对于WORKSPACE目录
- 非系统目录用
#include "foo/bar/baz.h"
, 系统目录用#include <foo/bar/baz.h>
- 不要使用
.
和..
- 对第三方库可以使用
include_prefix
和strip_include_prefix
有时候依赖第三方库的时候,这些库里的文件已有的include path如果放到当前目录,会不符合从workspace root引用文件的规则,就需要添加目录,比如下面的目录
1 | └── my-project |
以上,bazel要求some_lib.h
必须以legacy/some_lib/include/some_lib.h
这个形式包含,但some_lib.cc
现在是"include/some_lib.h"
这样包含的,要使这个include path有效,需要按如下方式指定路径(待验证):
1 | cc_library( |
包含多个文件
使用glob, 全局匹配,类似CMake。
1 | cc_library( |
- copts:C的编译选项
1 | cc_library( |
依赖处理
- bazel中的依赖不传递解析
如果包含了其他头文件,就要把这个头文件的target包含进来。这个头文件内部的include则不用管。比如
sandwich依赖bread,bread依赖flour,但sandwich不依赖flour。
1 | cc_library( |
- 单个头文件,如果没有实现,也需要定义target,比如
1 | cc_library( |
包含外部库
假设我们要使用Google Test,可以在WORKSPACE中这样指定:
1 | new_http_archive( |
【注】如果库已经包含了一个BUILD文件,可以使用 non-new_
函数。
创建文件gtest.BUILD
,这个文件用来编译Google Test,由于google test比较特殊的需求,所以它的编译规则会更复杂,特殊性在于:
googletest-release-1.7.0/src/gtest-all.cc
#include
了googletest-release-1.7.0/src/
下的所有文件,所以需要把这个文件排除掉- 它的头文件都是相对于这个目录的
googletest-release-1.7.0/include/
,比如"gtest/gtest.h"
,所以需要把这个目录加到copts的-I选项中 - 需要链接pthread
所以,最终编译规则如下:
1 | cc_library( |
这个看起来有点乱,因为里面包含了那个版本目录名,这个名字可以在new_http_archive
中使用strip_prefix
去掉:
1 | new_http_archive( |
去掉后的gtest.BUILD文件如下:
1 | cc_library( |
现在,其他的 cc_ rules
可以依赖于 @gtest//:main
。
更详细的cc rule说明参考 cc rules
编写测试用例
创建文件 ./test/hello-test.cc
1 |
|
创建 ./test/BUILD
1 | cc_test( |
注意,要使hello-greet
对hello-test
可见,需要在 ./lib/BUILD
文件中添加属性visibility
,值为 //test:__pkg__
。
运行测试用例:
1 | bazel test test:hello-test |
输出:
1 | INFO: Found 1 test target... |
该部分来自于 bazel C++ use case
包含预编译的库
- 动态库
1 | cc_library( |
处理外部依赖
Working with external dependencies
依赖bazel工程
依赖非bazel工程
需要编写BUILD文件
依赖隐藏(Shadowing)
- 依赖同一个package的不同版本
.bazelrc,Bazel 的配置文件
Bazel 有许多选项参数,一些参数是经常变化的,例如 –subcommands 还有一些是不怎么变化的。为了避免每次构建都写参数,你能够指定配置文件。
.bazelrc 文件位置
Bazel 配置文件在下列地方,按照先后顺序加载,所以后边的可以覆盖前边的选项。控制那个文件被加载是启动选项。
- system rc
/etc/bazel.bazelrc
如果其他系统需要,你必须自己构建 Bazel 二进制,并且使用 BAZEL_SYSTEM_BAZELRC_PATH 值 在 //src/main/cpp:option_processor. - workspace rc
$workspace/.bazelrc
- home rc
$HOME/.bazelrc
- 用户指定 rc
--bazelrc=file
除此之外,bazel 还会查找全局 rc 文件,参见 global bazelrc section
References: