-
简介
bazel
是Google开源的一套类似于Make的编译构建工具。- 运作原理
- 运行构建或测试时,Bazel执行以下操作
- 加载
BUILD
与目标相关的文件。 - 分析输入及其依赖关系,应用指定的构建规则。并生产
action
图 - 对输入执行构建操作,直到生成最终构建输出。
- 加载
- action图表示各个构建输入和他们之间的关系,以及Bazel将执行的构建操作。
- 运行构建或测试时,Bazel执行以下操作
- 运作原理
-
优点
- 构建快。支持增量编译, 对依赖关系进行了优化,从而支持并发执行。
- 可构建多种语言。bazel可用来构建Java C++ Android iOS等很多语言和框架,并支持mac windows linux等不同平台。
- 可伸缩。可处理任意大小的代码库,可处理多个库,也可以处理单个库
- 可扩展。使用bazel扩展语言可支持新语言和新平台。
-
安装方法
入门用法
项目结构
根目录下为工作区workspace
,workspace下包含多个package
,每个package又包含多个编译目标target
。
wrokspace
- Bazel的编译是基于工作区(workspace)的概念。工作区是一个存放了所有源代码和Bazel编译输出文件的目录,也就是整个项目的根目录。同时它也包含一些Bazel认识的文件:
WORKSPACE
文件,用于指定当前文件夹就是一个Bazel的工作区。所以**WORKSPACE
文件总是存在于项目的根目录下**。- 一个或多个
BUILD
文件,用于告诉Bazel怎么构建项目的不同部分。(如果工作区中的一个目录包含BUILD文件,那么它就是一个package。)
- 要指定一个目录为Bazel的工作区,就只要在该目录下创建一个空的
WORKSPACE
文件即可。 - 当Bazel编译项目时,所有的输入和依赖项都必须在同一个工作区。属于不同工作区的文件,除非linked否则彼此独立。
WORKSPACE
采用类似Python的语法
package
- 如果工作区中的一个目录包含BUILD文件,那么它就是一个
package
。
target
- 一个
BUILD
文件包含了几种不同类型的指令。其中最重要的是编译指令,它告诉Bazel如何编译想要的输出,比如可执行二进制文件或库。 BUILD
文件中的每一条编译指令被称为一个target
,它指向一系列的源文件和依赖,一个target也可以指向别的target。
编译示例
示例使用官方的c++编译示例
-
编译示例目录结构
examples
└── cpp-tutorial
├──stage1
│ ├── main
│ │ ├── BUILD
│ │ └── hello-world.cc
│ └── WORKSPACE
│
├──stage2
│ ├── main
│ │ ├── BUILD
│ │ ├── hello-world.cc
│ │ ├── hello-greet.cc
│ │ └── hello-greet.h
│ └── WORKSPACE
│
└──stage3
├── main
│ ├── BUILD
│ ├── hello-world.cc
│ ├── hello-greet.cc
│ └── hello-greet.h
├── lib
│ ├── BUILD
│ ├── hello-time.cc
│ └── hello-time.h
└── WORKSPACE
如何编译
1 2 3 4 | # 在WORKSPACE文件所在根目录下执行 # //main:是BUILD文件相对于WORKSPACE文件的位置 # hello-world则是我们在BUILD文件中命名好的target的名字 bazel build //main:hello-world |
Stage1 如何构建单个package中的单个target
1 2 3 4 5 | # BUILD 文件内容 cc_binary( name = "hello-world", srcs = ["hello-world.cc"], ) |
Stage2 如何构建单个package的多个target
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # BUILD 文件内容 cc_library( name = "hello-greet", srcs = ["hello-greet.cc"], hdrs = ["hello-greet.h"], ) cc_binary( name = "hello-world", srcs = ["hello-world.cc"], deps = [ ":hello-greet", ], ) |
Stage3 如何构建多个package的多个target
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # lib包 ## 默认情况, targets只对同一BUILD文件里的其他targets可见 ## 若要对其他包可见, 需声明为显示可见 ## 防止像共有API中库的实现细节泄露等情况 cc_library( name = "hello-time", srcs = ["hello-time.cc"], hdrs = ["hello-time.h"], visibility = ["//main:__pkg__"], ) # main包 cc_library( name = "hello-greet", srcs = ["hello-greet.cc"], hdrs = ["hello-greet.h"], ) cc_binary( name = "hello-world", srcs = ["hello-world.cc"], deps = [ ":hello-greet", "//lib:hello-time", ], ) |
如何查看依赖图
-
生成依赖图
1 2 3 4
# 查找target为 //main:hello-world 的所有依赖 # --nohost_deps 表示不包括host依赖 # --noimplicit_deps 表示不包括隐式依赖 e.g: @bazel_tools//tools/cpp:stl bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph
-
将生成的输出图文字描述, 粘贴到 GraphViz, 生成的依赖图如下
进阶用法
-
bazel 可选命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
用法:bazel <command> <options> ... 可用命令: build 构建指定的目标。 clean 删除输出文件,并可选择停止服务器。 run 运行指定的目标。 test 构建并运行指定的测试目标。 help 打印命令或索引的帮助。 info 显示有关bazel服务器的运行时信息。 version 打印Bazel的版本信息。 analyze-profile 分析构建配置文件数据。 aquery 对分析后的操作图执行查询。 cquery 执行分析后依赖图查询。 query 执行依赖关系图查询。 canonicalize-flags Canonicalize Bazel flags。 dump 转储Bazel服务器进程的内部状态。 fetch 获取目标的所有外部依赖项。 mobile-install 在移动设备上安装应用程序。 shutdown 停止Bazel服务器。
构建
- 用法
1 | bazel build <options> <targets> |
指定包路径
--package_path
指定搜索BUILD文件的目录集合
1 2 3 4 5 6 7 | mkdir -p build && cd build touch WORKSPACE ## 绝对路径 bazel build --package_path /some/other/path //main:hello-world ## 相对路径 bazel build --package_path %workspace%/relative/to/other/path //main:hello-world |
编译警告检查
--output_filter regex
显示那些符合正则表达式的构建和编译的警告,如果目标不匹配则标准输出和标准错误将会被丢弃。用于帮助找到某些特定的警告。
1 2 3 4 5 6 7 8 9 10 11 | ## 显示所有警告 bazel build --output_filter= --package_path /some/other/path ## 只显示指定的某几个包(project)的警告 --output_filter='^//(first/project|second/project):' ## 不显示指定的某几个包的警告 --output_filter='^//((?!(first/bad_project|second/bad_project):).)*$' ## 所有警告均不显示 --output_filter=DONT_MATCH_ANYTHING |
编译器的编译选项
编译C的Flag
--copt gcc-option
指定给 gcc 传递什么参数, 等价于 cmake 的 CMAKE_C_FLAGS
1 2 | ## 参数项一次只能指定一个 bazel build --copt="-O3" --copt="-fpic" --copt="-std=c++11" //main:hello-world |
只适用编译C的Flag
--conlyopt gcc-option
与 —copt 相似, 不同的是只能指定 c 的编译选项
1 2 | ## 参数项一次只能指定一个 bazel build --conlyopt="-Wno-pointer-sign" //main:hello-world |
gcc中 char 和 unsigned char 有时候传递参数类型不匹配会有报警,增加该编译选项可以关闭该报警。
编译C++的Flag
--cxxopt gcc-option
指定编 c++ 的参数项, 等价于 cmake 的 CMAKE_CXX_FLAGS
1 2 | ## 参数项一次只能指定一个 bazel build --cxxopt="-fno-implicit-templates" //main:hello-world |
用**-fno-implicit-templates**编译代码,会令隐式的模板实例化失效,他会显示的初始化所需模板。虽然这种方法需要精确了解正在使用的是哪种模板实例,但这种方法确实令源代码更加清楚。
编译器链接Flag
--linkopt linker-option
指定给 gcc 的链接库选项
1 | bazel build --linkopt="-pthread" //main:hello-world |
调试与符号信息的去除
--strip (always|never|sometimes)
指定是否从二进制文件和共享库文件中去除调试信息
1 2 | # 默认是 sometimes, 当语义项 --compilation_mode=fastbuild 时 bazel build --strip=always //main:hello-world |
注:
-
即使使用了
--strip=never
也不会保留 debug 信息, 如果想要得到完整符号库, 需指定使用-c dbg
或者--copt -g
方可1
bazel build -c dbg --strip=never //main:hello-world
-
--strip
项默认是 –strip-debug, 如果不只是想去除 debug 信息, 而是去除所有符号信息, 则需指定链接项--strip-all
1
bazel build --strip=always --linkopt=-Wl,--strip-all //main:hello-world
完整的编译选项文档: Commands and Options
语义选项
影响构建的命令和输出的内容。
指定编译模式
--compilation_mode (fastbuild|opt|dbg) (-c)
fastbuild
快速编译, 会产生最小的调试信息(-gmlt -Wl,-S), 同时将会设置 -DNDEBUG, 默认该模式opt
最优化, 不会产生任何调试信息, 除非手动指定了 –copt -g, 同时设置 -O2 -DNDEBUGdbg
调试, 产生调试信息(-g)
1 2 3 | bazel build --compilation_mode "dbg" //main:hello-world ## 或者使用缩写 bazel build -c "dbg" //main:hello-world |
动态链接模式
--dynamic_mode (auto|default|fully|off)
auto
根据平台选择: linux 上是 default, cygwin 上是 offdefault
由 bazel 去选择是否进行动态链接fully
动态链接所有目标, 将会加速链接时间和减小输出库大小off
静态链接所有目标, 当 linkopts 指定设置 -static, 将自动转成该模式
输出构建过程的执行语句
--explain logfile
写到一个日志文件当中, 配合 —-verbose_explanations
, 效果等同 cmake 开启 CMAKE_VERBOSE_MAKEFILE
1 | bazel build --explain "log.txt" -—verbose_explanations //main:hello-world |
输出构建过程的性能
--profile file
会把构建的分析数据写到一个二进制文件当中, 可使用 bazel analyze-profile
命令进行解析
1 2 3 4 5 | ## 生成 profile bazel build --profile "profile.bin" //main:hello-world ## 分析构建性能 bazel analyze-profile profile.bin |
**更多构建命令选项可参考: ** bazel help build
查看构建的信息内容
-
命令用法
1
bazel info <options> [key]
-
--show_make_env
显示构建环境, 默认 false
**更多信息查询命令选项可参考: ** bazel help info
分析
- 命令用法
1 | bazel query <options> <query-expression> |
是否分析host依赖相关
--host_deps
指定分析其依赖关系, 默认开启
--nohost_deps
指定不分析其依赖关系
是否分析其隐式依赖关系
--implicit_deps
指定分析其隐式依赖关系, 例如 @bazel_tools//tools/cpp:malloc, 默认开启
--noimplicit_deps
指定不分析其隐式依赖关系
指定输出格式
--output (build|graph|label|label_kind|location|maxrank|minrank|package|proto|xml)
build
输出指定分析目标的 BUILD 文件内容
1 2 3 4 5 | # /Users/wangzhuxing/Desktop/Project/repository/wzx3/bazel-examples/cpp-tutorial/stage1/main/BUILD:1:1 cc_binary( name = "hello-world", srcs = ["//main:hello-world.cc"], ) |
-
graph
输出可进行图可视化的格式文本1 2 3 4 5 6
digraph mygraph { node [shape=box]; "//main:hello-world" "//main:hello-world" -> "//main:hello-world.cc" "//main:hello-world.cc" }
-
label
输出指定分析目标的依赖文件, 默认配置1 2
//main:hello-world //main:hello-world.cc
-
label_kind
分类输出指定分析目标的依赖1 2
cc_binary rule //main:hello-world source file //main:hello-world.cc
-
location
输出指定分析目标的代码行位置1 2
/Users/.../main/BUILD:1:1: cc_binary rule //main:hello-world /Users/.../main/BUILD:3:12: source file //main:hello-world.cc
-
maxrank|minrank
按等级排列指定分析目标的依赖1 2
0 //main:hello-world 1 //main:hello-world.cc
-
package
输出指定分析目标的包名1
main
实用流程
- 生成可视化图结构的信息文本
1 2 3 4 | bazel query --nohost_deps \ --noimplicit_deps \ 'deps(//main:hello-world)' \ --output graph > output-graph.gv |
- 通过 python 的
graphviz
模块 进行图解析
1 | python -m bazel_graphviz --input_file="output-graph.gv" |
附上本人的解析代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # encoding: utf-8 import argparse, os from graphviz import Source def _render(args): if args.input_file: output_file_name, output_file_extension = os.path.splitext(str(args.input_file)) src = Source.from_file(str(args.input_file)) src.format = 'png' src.render(filename=output_file_name, view=True, cleanup=True) return True return False def _get_parser(): parser = argparse.ArgumentParser() parser.add_argument( '--input_file', default=None, help='Path to the model weights file of the external tool (e.g caffe/onnx weights proto binary)') return parser def _main(): parser = _get_parser() args, unknown_args = parser.parse_known_args() if not _render(args): print('Render bazel graph failed!') if __name__ == '__main__': _main() |
**更多构建命令选项可参考: ** bazel help query
清理
只清理编译目录
删除编译项目的编译过程中产生的目录树,不会删除bazel产生的临时文件
1 | bazel clean |
完全清理
删除输出文件的目录树,删除Bazel产生的所有临时文件,删除下载的临时文件等
1 | bazel clean --expunge |
测试
- 命令用法
1 | bazel test <options> <test-targets> |
注:
- 命令运行前会先执行一次 bazel build
- 测试命令无法直接传入测试程序的形参, 也并不需要
**更多测试命令选项可参考: ** bazel help test
运行
- 命令用法
1 | bazel run <options> -- <binary target> <flags to binary> |
**更多运行命令选项可参考: ** bazel help run
高阶用法
BUILD 文件语法
概念和术语
通用定义
-
Bourne shell 标记化
根据
Bourne shell
的标记化规则,某些规则的某些字符串属性被拆分为多个单词:- 未加引号的空格分隔单独的单词
- 单引号和双引号字符和反斜杠用于防止标记化
受此标记化影响的属性在本文档的定义中明确指出。
受**“Make”变量扩展和Bourne shell标记化的属性通常用于将任意选项传递给编译器和其他工具**。此类属性的示例是
cc_library.copts
和java_library.javacopts
。这些替换一起允许单个字符串变量扩展为特定于配置的选项字列表。 -
标签表达式
-
编译(build)规则的通用属性
-
测试(test)规则的通用属性 (*_test)
-
生成binary规则的通用属性 (*_binary)
-
可配置的属性
-
隐式输出目标
"Make"变量
函数接口