0.安装
- Bazel构建C++项目学习
- bazel github下载, 下载二进制直接安装就行
- 参考备忘
# bazel-5.3.0-installer-linux-x86_64.sh
chmod +x bazel-version-installer-linux-x86_64.sh
./bazel-version-installer-linux-x86_64.sh
执行输出信息:
Bazel is now installed!
Make sure you have "/usr/local/bin" in your path.
For bash completion, add the following line to your ~/.bashrc:
source /usr/local/lib/bazel/bin/bazel-complete.bash
For fish shell completion, link this file into your
/home/q/.config/fish/completions/ directory:
ln -s /usr/local/lib/bazel/bin/bazel.fish /home/q/.config/fish/completions/bazel.fish
See http://bazel.build/docs/getting-started.html to start a new project!
根据提示,将 source /usr/local/lib/bazel/bin/bazel-complete.bash
添加到 ~/.bashrc 中:
echo 'source /usr/local/lib/bazel/bin/bazel-complete.bash' >> ~/.bashrc
source ~/.bashrc
测试安装:
bazel --version # bazel 5.3.0
接下来以官方给出的 demo进行学习,这个项目有 C++、java、android、java-maven等示例,进入 C++ 项目示例即可。
.
├── README.md
├── stage1
│ ├── main
│ │ ├── BUILD
│ │ └── hello-world.cc
│ ├── README.md
│ └── WORKSPACE
├── stage2
│ ├── main
│ │ ├── BUILD
│ │ ├── hello-greet.cc
│ │ ├── hello-greet.h
│ │ └── hello-world.cc
│ ├── README.md
│ └── WORKSPACE
└── stage3
├── lib
│ ├── BUILD
│ ├── hello-time.cc
│ └── hello-time.h
├── main
│ ├── BUILD
│ ├── hello-greet.cc
│ ├── hello-greet.h
│ └── hello-world.cc
├── README.md
└── WORKSPACE
1.实例代码
stage1、stage2、stage3 是三个独立的示例工程项目(三个工程之间没有直接关系):
- 示例 stage1 演示了一个简单的 C++ 程序
- 示例 stage2 演示了一个 .cc 文件需要引用另一个 .cc 文件的情况
- 示例 stage3 演示了一个 .cc 文件和 main 程序不在同一个目录下的情况(一个在 main 目录,一个在 lib 目录)
2.设置工作区
工作区是保存项目源文件和 Bazel 构建输出的目录。bazel相关的:
- WORKSPACE : 工作空间,在项目工程的根(root)目录下,有个 WORKSPACE 文件,表示这是一个工程项目。比如,stage1 目录下有 WORKSPACE 文件,说明这个目录下有一个完整的工程项目。
- BUILD : BUILD 文件,这个文件会告诉 bazel 如何编译 .cc 源文件,限定多个源文件之间的关系。工作空间中的可能有多个文件夹,每个目录有 代码源文件 与 BUILD 文件,这些有 BUILD 文件的文件夹,被称为 包(pakege)。
- Bazel 编译构建一个项目(project)时,所有的输入文件和依赖文件,都应该要求在同一个工作空间(WORKSPACE)中。 虽然可以使用某种链接方式,达到使用其它工作空间的文件的目的,但一般不这么使用。
3.认识 BUILD 文件
BUILD 文件包含了一系列不同类型的指令,Bazel 工具执行这些指令,来构建和编译目标(target: 想生成的东西,如源代码生成的二进制可执行文件、.o中间文件、lib 库文件等等):
BUILD 文件中,最重要命令的就是 构建规则(build rule),它告诉 Bazel 如何去创建和生成我们的目标。比如 cpp-tutorial/stage1/main 目录下的 BUILD 文件,内容如下:
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "hello-world",
srcs = ["hello-world.cc"],
)
上面规则中, hello-world 就是目标target, hello-world.cc 就是依赖文件,意思就是说,使用 hello-world.cc 文件,编译生成一个想要的名为 hello-world 的可执行文件。
4.stage1-单个目标,单个软件包
.
├── main
│ ├── BUILD
│ └── hello-world.cc
├── README.md
└── WORKSPACE
对stage-1进行编译: bazel build //main:hello-world
-
//main
: 是相对路径,相对于工作区间 workspace 的根目录的路径 -
hello-world
是目标target -
Bazel工具使用 main 目录下 BUILD 这个文件指定的编译规则,来编译生成目标. 编译过程,打印下面信息:
Starting local Bazel server and connecting to it... INFO: Analyzed target //main:hello-world (37 packages loaded, 163 targets configured). INFO: Found 1 target... Target //main:hello-world up-to-date: bazel-bin/main/hello-world INFO: Elapsed time: 6.254s, Critical Path: 0.47s INFO: 6 processes: 4 internal, 2 linux-sandbox. INFO: Build completed successfully, 6 total actions
这时,项目空间的根目录下,生成目录 bazel-bin (生成了一些快捷方式,文件原位置在缓存中),这个目录中,包含有刚才编译生成的目标:
.
├── bazel-bin -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__/bazel-out/k8-fastbuild/bin
├── bazel-out -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__/bazel-out
├── bazel-stage1 -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__
├── bazel-testlogs -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__/bazel-out/k8-fastbuild/testlogs
├── main
│ ├── BUILD
│ └── hello-world.cc
├── README.md
└── WORKSPACE
执行编译生成的可执行文件 bazel-bin/main/hello-world
Hello world
Wed Oct 12 13:55:46 2022
5.查看依赖关系图
查看目标是依赖哪些文件生成,即可视化项目的依赖关系,可以通过在工作区根目录运行如下命令,来生成依赖关系图的文本表示:
bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" --output graph
执行结果如下:
digraph mygraph {
node [shape=box];
"//main:hello-world"
"//main:hello-world" -> "//main:hello-world.cc"
"//main:hello-world.cc"
}
上面的命令告诉 Bazel 查找目标 //main:hello-world 的所有依赖项(不包括主机和隐式依赖项)并将输出格式化为图形。然后,将文本粘贴到 GraphViz 中进行可视化。
此外,还可以通过安装 GraphViz 和 xdot Dot Viewer 在本地查看图形:
sudo apt update && sudo apt install graphviz xdot
然后,您可以通过将上面的文本输出直接传送到 xdot 来生成和查看图形:
xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" --output graph
6.stage2-构建多个目标
在stage1示例中,只有一个 .cc 源文件,但实际项目开发中,一般会有多个 .cc 源文件。如何编译多个 .cc 源文件这种情况。先进入示例 stage2 工程目录下.
.
├── main
│ ├── BUILD
│ ├── hello-greet.cc
│ ├── hello-greet.h
│ └── hello-world.cc
├── README.md
└── WORKSPACE
查看该项目下 main 文件夹下的 BUILD 文件,内容如下:
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
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",
],
)
- Bazel 会先编译 hello-greet 这个目标,这个目标是一个 C++ 的 lib 库文件(使用 bazel 内建规则 cc_library rule 来生成这个lib)
- 然后,Bazel 再编译 hello-world 这个目标,这个目标是一个二进制可执行文件。deps 这个属性,告诉 Bazel 编译 hello-world 时,需要用到 hello-greet 这个目标,也就是 hello-world 的生成需要依赖(dependencies) hello-greet 。
执行命令来编译 stage2 这个工程:
bazel build //main:hello-world
编译完成以后,尝试执行一下生成的二进制文件:
bazel-bin/main/hello-world
打印结果如下:
Hello world
Wed Oct 12 14:13:25 2022
如果这时候,去修改 hello-greet.cc 这个源文件,那么,bazel 只会重新编译 ``hello-greet.cc` 这个文件,其它文件不会被重新编译。
查看一下依赖关系图:
xdot <(bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph)
7.stage3-多个软件包
可以将项目拆分为多个包,看一下cpp-tutorial/stage3目录的内容.
.
├── lib
│ ├── BUILD
│ ├── hello-time.cc
│ └── hello-time.h
├── main
│ ├── BUILD
│ ├── hello-greet.cc
│ ├── hello-greet.h
│ └── hello-world.cc
├── README.md
└── WORKSPACE
现在有两个子目录,每个子目录都包含一个 BUILD 文件。 因此,对于 Bazel,工作区现在包含两个包: lib 和 main
-
lib/BUILD 文件:
load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( name = "hello-time", srcs = ["hello-time.cc"], hdrs = ["hello-time.h"], visibility = ["//main:__pkg__"], #对 main 包可见 )
-
main/BUILD 文件:
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") 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", ], )
-
未编译也可(通过 BUILD) 查看依赖关系:
xdot <(bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \ > --output graph)
-
编译并执行
bazel build //main:hello-world bazel-bin/main/hello-world
输出:
Hello world Wed Oct 12 14:35:26 2022
8.其他常用案例
-
- 在目标中包含多个文件
cc_library( name = "build-all-the-files", srcs = glob(["*.cc"]), hdrs = glob(["*.h"]), )
- 使用传递性包含
- 添加包含路径
- 包括外部库
- 编写并运行 C++ 测试
- 添加对预编译库的依赖关系
cc_library( name = "mylib", srcs = ["mylib.so"], hdrs = ["mylib.h"], )
- 在目标中包含多个文件
-
- cc_binary
- cc_import
- cc_library
- cc_proto_library
- fdo_prefetch_hints
- fdo_profile
- propeller_optimize
- cc_test
- cc_toolchain
- cc_toolchain_suite
-
- package(default_deprecation, default_testonly, default_visibility, features)
- package_group(name, packages, includes)
- exports_files([label, …], visibility, licenses)
- glob(include, exclude=[], exclude_directories=1, allow_empty=True)
- select(
{conditionA: valuesA, conditionB: valuesB, …},
no_match_error = “custom message”
) - subpackages(include, exclude=[], allow_empty=True)