1、Bazel简介
相信大家对于make和cmake都或多或有写了解,这里不放回顾下。一句话:
cmake是makefile的上层工具,其目的正是为了生成可移植的makefile,简化手写makefile时的巨大工作量。
关于 makefile 的编写可以参见: 这里。
Bazel则是一款与Make、Maven类似的构建工具。最初是google为其内部软件量身定制的工具,如今支持绝大多数软件的构建。特点大概如下:
(1)多语言支持:C++、Java等,也可以扩展到其他编程语言。
(2)高级构建描述语言:项目使用一种叫BUILD的语言来描述,他是一种简洁的文本语言。它把项目视为一个集合,这个集合由一些互相关联的库、二进制文件和测试用例组成。相反,像Make这样的工具则需要去描述每个文件如何调用编译器(确实)。
(3)多平台支持:在google,Bazel甚至被同时用在服务器应用和手机端应用。
(4)可重复性:在BUILD文件中,每个库、测试用例和二进制文件都需要明确指定它们的依赖关系。当一个源码文件被修改时,Bazel根据依赖关系就知道哪些需要重新构建、以及哪些任务可以并行。这意味着所有构建都是增量的。
(5)可伸缩性:Bazel可以处理大型项目。
疑问:为什么要重新发明一个构建工具而不是直接使用Make?
google认为Make控制得太细,以至于最终的结果完全依靠开发人员。而且,使用cmake自动生成的makefile构建软件速度太慢了。
相关资料:
https://bazel.build/docs?hl=zh-cn #中文官网啥都有
Tags · bazelbuild/bazel · GitHub #git链接
2、Bazel安装
安装 bazel 参见 这里。
Installing Bazel - Bazel 3.7.0
方法一:下载.repo并通过yum安装(没尝试)
Installing Bazel on Fedora and CentOS - Bazel 3.7.0
方法二:使用Bazel官方安装脚本(后续用的时候还要下载东西 没成功)
curl -OL https://github.com/bazelbuild/bazel/releases/download/7.5.0/bazel-7.5.0-installer-linux-x86_64.shchmod a+x bazel-7.5.0-installer-linux-x86_64.sh ./bazel-7.5.0-installer-linux-x86_64.sh
方法三:使用docker(用起来不符合预期)
#先查一下bazel
docker search register.librax.org/bazel#选一个star最多的pull下来
docker pull register.librax.org/insready/bazel#
docker run -it register.librax.org/insready/bazel:latest register.librax.org/insready/bazel --version
方法四:macos安装(✅)
通过Homebrew安装是可以的。
https://bazel.build/install/os-x?hl=zh-cn
验证方式。
#能正常输出版本就ok了
bazel --version
3、Bazel相关概念
3.1、WORKSPACE(工作区)
workspace是Bazel中的一个概念,其实就是目录。这个目录是bazel工作时的一个基准目录。
bazel规定 项目源文件和Bazel构建出的目标文件都要放在这个目录下。如果想把一个目录设置为workspace,必须要在此目录创建一个名为 WORKSPACE. Bazel 的空文件。构建项目时,所有的输入项和依赖项都必须位于同一个工作区内。
3.2、BUILD文件
BUILD文件的作用就类似于Makefile之于Make、xml文件之于Maven。
BUILD文件中包含构建规则在内的各种不同指令,其中最重要的就是构建规则(build rule)。该规则告诉Bazel如何构建所需的数据,例如是输出二进制文件还是输出库等。
3.3、target
BUILD文件中的每一条编译指令被称为一个target,它指向一系列的源文件和依赖,一个target也可以指向别的target。
3.4、package
参见示例stage3。目录下有两个子目录 main 和 lib,且两个子目录内都包含了 BUILD 文件。此时,main 和 lib 就被称为package(包)。
└──stage3├── main│ ├── BUILD│ ├── hello-world.cc│ ├── hello-greet.cc│ └── hello-greet.h├── lib│ ├── BUILD│ ├── hello-time.cc│ └── hello-time.h└── WORKSPACE
此时 main 和 lib 都被称为 stage3 项目下的两个包。引入了package的概念后 源文件可以再项目空间的不同目录下。
3.5、构建规则
构建规则 | 描述 |
cc_binary | 生成可执行文件 |
cc_library | 生成库文件 |
cc_test | 运行,相当于 cc_binary + cc_library |
cc_import | 导入预先编译的库(静态库、共享库) |
name | 目标名称 |
srcs | 用以构建 C++ 目标所需要的文件列表 (包括头文件、源文件、编译中间文件) |
deps | 需要链接到目标的库,通常是 cc_library 目标 |
visibility | 声明当前 target 的可见性,即谁可以使用这个 target ,没有此参数(默认情况)时 target 仅对同一个 BUILD文件中的其他 target 可见。visibility = ["//visibility:public"]表明该库对所有包可见 |
linkstatic | 是否将依赖库静态编译到目标中 |
copts | 添加编译参数 |
3.6、bazel命令
参考:https://bazel.build/reference/command-line-reference?hl=zh-cn
命令 | 描述 |
---|---|
bazel build | 编译 |
bazel test | 测试 |
build run | 运行,相当于 bazel build + bazel test |
4、Bazel使用体验
参见: https://bazel.build/versions/6.4.0/start/cpp?hl=zh-cn
4.0、下载示例项目
执行如下指令下载 官网示例项目。
git clone https://github.com/bazelbuild/examples
示例项目位于 examples/cpp-tutorial 目录中。其结构如下:
[root@SNG-Qcloud /data/home/bazel/examples/cpp-tutorial]# tree
.
├── README.md
├── stage1
│ ├── main
│ │ ├── BUILD
│ │ └── hello-world.cc
│ ├── MODULE.bazel
│ └── README.md
├── stage2
│ ├── main
│ │ ├── BUILD
│ │ ├── hello-greet.cc
│ │ ├── hello-greet.h
│ │ └── hello-world.cc
│ ├── MODULE.bazel
│ └── README.md
└── stage3├── lib│ ├── BUILD│ ├── hello-time.cc│ └── hello-time.h├── main│ ├── BUILD│ ├── hello-greet.cc│ ├── hello-greet.h│ └── hello-world.cc├── MODULE.bazel└── README.md7 directories, 20 files
注:可以看到每个示例目录下都有一个 MODULE.bazel 文件,标识该目录为工作区。
有三组文件,每组文件代表本教程中的一个阶段。在第一阶段,演示构建一个位于单个软件包中的单个目标。在第二阶段,演示从单个软件包构建二进制文件和库。在第三个阶段,演示构建一个包含多个软件包的项目,并使用多个目标构建该项目。
4.1、第一阶段:单package单target
查看 cpp-tutorial/stage1/main 目录中的 BUILD 文件 如下。
cc_binary(name = "hello-world",srcs = ["hello-world.cc"],
)
该BUILD文件中出现的 “cc_binary” 就是前面所说的构建规则。
该规则用于构建C或C++的二进制可执行文件。具体参数参见:这里。
上述BUILD内容对应一个编译指令,即一个target。
#跳转工作区目录
cd examples/cpp-tutorial/stage1
#运行bazel进行构建
bazel build //main:hello-world
其中:
//main 表示 BUILD 文件相对于工作区根目录的路径;
hello-world 表示 BUILD 文件中的目标target;
@//main 表示 BUILD 文件相对于主工作区workspace的路径;
输出如下:
INFO: Analyzed target //main:hello-world (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //main:hello-world up-to-date:bazel-bin/main/hello-world
INFO: Elapsed time: 0.117s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
于是我们成功的构建了第一个Bazel目标。Bazel会将构建输出放置在工作区的根目录下的 bazel-bin 目录中,具体为 “bazel-bin/main/hello-world”。执行结果如下:
查看依赖关系图:
成功的构建将其所有依赖项明确显示在BUILD
文件中。Bazel使用这些语句来创建项目的依赖关系图,从而实现准确的增量构建。
让我们可视化示例项目的依赖关系。首先,生成依赖关系图的文本表示(在工作空间根目录运行命令):
bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \--output graph
这个程序的依赖关系如下:
结论:我们已经完成了第一个 build,对 build 的结构有了基本的了解。接下来我们添加其他目标来增加复杂性。
4.2、第二阶段:单package构建二进制文件和库
单个target只能够满足小型项目的需求,对于大型项目我们可以通过拆分成多个target和程序包来实现快速增量构建、并通过一次构建项目的多个部分来加快构建速度。
第二阶段用到的文件在 stage2。
文件组织如下:
.
├── MODULE.bazel
├── README.md
└── main├── BUILD├── hello-greet.cc├── hello-greet.h└── hello-world.cc2 directories, 6 files
其中 cpp-tutorial/stage2/main
目录中的 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",],
)
看这个 BUILD 文件示例项目构建分成了两个target,分别用了不同的规则。
1)首先,使用Bazel内置的 cc_library规则 构建了一个 hello-greet 的库。
2)然后,使用Bazel内置的 cc_binary规则 构建二进制文件 hello-world。其中 deps 属性告知Bazel 需要 hello-greet 库才能构建 hello-world 二进制文件。
cd stage2
bazel build //main:hello-world
bazel产生类似如下的输出:
Starting local Bazel server and connecting to it...
INFO: Analyzed target //main:hello-world (87 packages loaded, 450 targets configured).
INFO: Found 1 target...
Target //main:hello-world up-to-date:bazel-bin/main/hello-world
INFO: Elapsed time: 13.018s, Critical Path: 0.78s
INFO: 14 processes: 10 internal, 4 darwin-sandbox.
INFO: Build completed successfully, 14 total actions
如果现在修改 hello-greet.cc 并重建项目,Bazel将仅重新编译该文件。
查看依赖关系图,您可以看到hello-world与以前一样依赖于相同的输入,但是构建的结构不同:
4.3、第三阶段:多package多target
参见 stage3 目录。
.
├── MODULE.bazel
├── README.md
├── lib
│ ├── BUILD
│ ├── hello-time.cc
│ └── hello-time.h
└── main├── BUILD├── hello-greet.cc├── hello-greet.h└── hello-world.cc3 directories, 9 files
可以看到。现在有两个子目录,每个子目录都包含一个 BUILD 文件;即分成了 main 和 lib 两个package(包)。其中。
lib/BUILD文件如下:
cc_library(name = "hello-time",srcs = ["hello-time.cc"],hdrs = ["hello-time.h"],visibility = ["//main:__pkg__"],
)
main/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","//lib:hello-time",],
)
可以看到,hello-world
这个 target 在 package main
中,需要依赖 target hello-time
,但是 hello-time
这个 target 在 package lib
中。默认情况下,target 只在同一个 BUILD
中有效,其它 BUILD
不可见,也就是无法使用。为了让其它 package 下的 target 也能使用,那么,只需加上 visibility
即可。比如上面的 visibility = ["//main:__pkg__"]
就是告诉 Bazel ,hello-time 这个 target ,main 这个包可以看到,这样 hello-world
就可以使用 hello-time
这个target 了。
这样,每个 package 目录下有一个自己的 BUILD
,既可以做到相互独立,也可以做到相互调用,减少耦合,利用维护。你同样可以通过下面命令编译项目 stage3 :
bazel build //main:hello-world