从零学习构造系统之bazel示例详解

什么是bazel

Bazel 是一款与 Make、Maven 和 Gradle 类似的开源构建和测试工具。 它使用人类可读懂的高级 build 语言。Bazel 支持使用多种语言的项目,并针对多个平台构建输出。Bazel 支持跨多个代码库和大量用户的大量代码库。

Bazel的基本概念

WORKSPACE file

在构建项目之前,我们需要先设置工作区,工作区的作用是存储项目的源文件和Bazel的构建输出的目录,其中WORKSPACE file就是将目录及其内容标识为Bazel工作区的文件,需要位于项目目录结构的根目录下,该文件可以为空,但是通常包含从网络或者本地文件系统中提取其他依赖项的外部代码库声明。

BUILD file

一个项目中包含一个或多个BUILD file,BUILD主要用于告知Bazel如何构建项目,工作区包含一个BUILD文件的目录就是一个软件包。在之前的文章提到过,Bazel是基于工件的构建系统,而工件就可以理解为Bazel根据Build文件中的编译规则编译该目录下的文件形成的,软件包可以包含子软件包或包含BUILD文件的子目录,从而形成软件包的层次。

BUILD文件中包含了一些适用于Bazel的不同类型的指令,每个BUILD文件都需要包含至少一条规则(如cc_library)作为一组指令,以指示Bazel如何构建所需要的输出,例如可执行文件或库。BUILD文件中的每一个build规则实例都称为目标,并指向一组特定的源文件和依赖项,一个目标也可以指向其他目标。以下就是一个简单的BUILD文件的例子。

cc_binary(
 name = "hello-world",
 srcs = ["hello-world.cc"],
)

在本示例中,hello-world 目标会实例化 Bazel 的内置 cc_binary rule。该规则会告知 Bazel 从 hello-world.cc 源文件构建不含依赖项的独立可执行文件。

Bazel的C++事例

该例子是Bazel官方提供的,github地址为git clone github.com/bazelbuild/…

单个目标,单个软件包

我们从最简单的例子开始,例子只包含一个源文件,需要将改源文件构建成一个可执行文件,其文件目录如下。

examples
└── cpp-tutorial
 └──stage1
 ├── main
 │ ├── BUILD
 │ └── hello-world.cc
 └── WORKSPACE

BUILD文件内容如下

load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
 name = "hello-world",
 srcs = ["hello-world.cc"],
)

然后进入该目录cpp-tutorial/stage1,运行以下命令构建

bazel build //main:hello-world

会打印以下信息表示编译成功

Starting local Bazel server and connecting to it...
INFO: Analyzed target //main:hello-world (37 packages loaded, 255 targets configured).
INFO: Found 1 target...
Target //main:hello-world up-to-date:
bazel-bin/main/hello-world
INFO: Elapsed time: 28.089s, Critical Path: 2.78s
INFO: 6 processes: 4 internal, 2 darwin-sandbox.
INFO: Build completed successfully, 6 total actions

然后可以使用以下命令测试,会打印Hello world

bazel-bin/main/hello-world

其依赖关系如下

单个软件包,多个目标

虽然单个目标足以满足小型项目的需求,在实际的开发过程中可能需要将较大的项目拆分为多个目标和软件包。这样可以实现快速增量构建(即 Bazel 仅重建更改的内容,并通过一次性构建项目的多个部分来加快构建速度。在教程的这一阶段,您将添加目标,然后添加软件包。

下面的BUILD文件会告诉Bazel先构建hello-greet库(使用Bazel内置的cc_library)然后构建hello-world二进制文件,其中的deps属性告诉Bazel构建hello-world需要hello-greet库。

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",
 ],
)

编译情况如下

INFO: Analyzed target //main:hello-world (36 packages loaded, 258 targets configured).
INFO: Found 1 target...
Target //main:hello-world up-to-date:
bazel-bin/main/hello-world
INFO: Elapsed time: 15.548s, Critical Path: 2.39s
INFO: 8 processes: 4 internal, 4 darwin-sandbox.
INFO: Build completed successfully, 8 total actions

如果现在修改 hello-greet.cc 并重新构建项目,Bazel 只会重新编译该文件,编译结果如下(通过-s选项可以打印具体编译过程)

INFO: Analyzed target //main:hello-world (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
SUBCOMMAND: # //main:hello-greet [action 'Compiling main/hello-greet.cc', configuration: a42135a42aad3da7e3af209ce54745fb0d0306dc29e1f3dc84d7d58372421fc9, execution platform: @local_config_platform//:host]
(cd /private/var/tmp/_bazel_qiming/e4d33fbb5ee1f924b3cb079f19abf4eb/execroot/__main__ && \
 exec env - \
 APPLE_SDK_PLATFORM=MacOSX \
 APPLE_SDK_VERSION_OVERRIDE=13.1 \
 PATH=/Users/qiming/Library/Caches/bazelisk/downloads/bazelbuild/bazel-6.0.0-darwin-x86_64/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin \
 XCODE_VERSION_OVERRIDE=14.2.0.14C18 \
 ZERO_AR_DATE=1 \
 external/local_config_cc/wrapped_clang_pp '-D_FORTIFY_SOURCE=1' -fstack-protector -fcolor-diagnostics -Wall -Wthread-safety -Wself-assign -fno-omit-frame-pointer -O0 -DDEBUG '-std=c++11' 'DEBUG_PREFIX_MAP_PWD=.' -iquote . -iquote bazel-out/darwin-fastbuild/bin -MD -MF bazel-out/darwin-fastbuild/bin/main/_objs/hello-greet/hello-greet.d '-DBAZEL_CURRENT_REPOSITORY=""' '-frandom-seed=bazel-out/darwin-fastbuild/bin/main/_objs/hello-greet/hello-greet.o' -isysroot __BAZEL_XCODE_SDKROOT__ -F__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks -F__BAZEL_XCODE_DEVELOPER_DIR__/Platforms/MacOSX.platform/Developer/Library/Frameworks -no-canonical-prefixes -pthread -no-canonical-prefixes -Wno-builtin-macro-redefined '-D__DATE__="redacted"' '-D__TIMESTAMP__="redacted"' '-D__TIME__="redacted"' -target x86_64-apple-macosx13.1 -c main/hello-greet.cc -o bazel-out/darwin-fastbuild/bin/main/_objs/hello-greet/hello-greet.o)
# Configuration: a42135a42aad3da7e3af209ce54745fb0d0306dc29e1f3dc84d7d58372421fc9
# Execution platform: @local_config_platform//:host
Target //main:hello-world up-to-date:
 bazel-bin/main/hello-world
INFO: Elapsed time: 0.750s, Critical Path: 0.58s
INFO: 2 processes: 1 internal, 1 darwin-sandbox.
INFO: Build completed successfully, 2 total actions

其中依赖如下图

多个软件包,多个目标

在包含多个软件包的情况下又该如何编译呢?其结构如下:

└──stage3
 ├── main
 │ ├── BUILD
 │ ├── hello-world.cc
 │ ├── hello-greet.cc
 │ └── hello-greet.h
 ├── lib
 │ ├── BUILD
 │ ├── hello-time.cc
 │ └── hello-time.h
 └── WORKSPACE

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目录下的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",
 ],
)

主软件包中的 hello-world 目标依赖于 lib 软件包中的 hello-time 目标(因此是目标标签 //lib:hello-time)- Bazel 通过 deps 属性知道这一点。依赖项图中反映了这一点:

为了顺利构建,请使用可见性属性使 lib/BUILD 中的 //lib:hello-time 目标明确对 main/BUILD 中的目标可见。这是因为默认情况下,目标仅对同一 BUILD 文件中的其他目标可见。Bazel 使用目标可见性来防止出现包含实现细节的库泄露到公共 API 等问题。

最后

作者:月下西楼

%s 个评论

要回复文章请先登录注册