使用 Bazel 构建项目

如何让项目的构建更有效率是工程中不可忽视的问题,特别是在项目的规模不断增大之后。传统的 C/C++ 项目,有诸如 Make、CMake 这样的工具,Java 项目则有 Maven/Gradle。但在真正企业级的大型项目里,可能会同时有 C++、Python、Go 或者 Java 项目,并且互相可能会有依赖关系。

Bazel 便是由 Google 开发和开源的项目构建测试工具,支持多种语言多个平台。Bazel 凭借其企业级的背景、远程执行的特性,被许多公司使用。Dropbox 在 2019 年末的博文中介绍了全面使用 Bazel 作为 CI/CD 工具。这篇文章将会以一个简单的项目为例,介绍 Bazel 的基本使用。

Bazel 的安装相对比较简单,在它的 GitHub Releases 页面就可以下载对应的二进制安装包。

构建 C++ 项目

Bazel 中比较重要的两个文件是:

  • WORKSPACE:含有该文件的目录将会被视为根目录
  • BUILD:含有该构建规则文件的目录被视为项目的一个模块

下面以官方的 examples 为例。首先克隆项目到本地:

1
$ git clone https://github.com/bazelbuild/examples

进入 examples/cpp-tutorial/stage1 目录下,有一个 WORKSPACE 文件,因为没有用到自定义的一些规则,所以该文件是空的。在 main 和目录下面有 BUILD 文件,包含了构建的目标:

1
2
3
4
5
6
load("@rules_cc//cc:defs.bzl", "cc_binary")

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

第一行 load 导入了内置的 cc_binary 规则,之后的 hello-world 则实例化了这个规则,通过 srcs 参数指定了源文件。

stage1 目录下,通过命令 $ bazel run //main:hello-world 即可编译并执行 hello-world 目标。如果是使用 bazel build 命令的话则只是进行编译,构建的目标则是相对于根目录 //main 目录下的 BUILD 里声明的 hello-world 目标。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ bazel run //main:hello-world
Starting local Bazel server and connecting to it...
INFO: Analyzed target //main:hello-world (14 packages loaded, 47 targets configured).
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 19.863s, Critical Path: 0.72s
INFO: 2 processes: 2 linux-sandbox.
INFO: Build completed successfully, 6 total actions
INFO: Build completed successfully, 6 total actions
Hello world

stage2 的例子里,展示了如何使用 cc_library 库并定义依赖关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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",
    ],
)

同样的,在 load 里引入 cc_binarycc_library 两个规则。hello-world 目标在 deps 中指定了依赖当前目录下的 hello-greet 模块。如果需要用到其它目录下的模块,在 stage3 下面就有例子,deps 中的模块需要像 "//lib:hello-time" 这样指定根目录下的子模块。

构建 Python 项目

在 Bazel 中使用 Python,需要引入自定义的 Python 规则。Bazel 官方开源了 rules_python

首先确保默认的 Python 在 /usr/bin/python 路径下,可以输入 which python 来确认。如果没有的话,需要手动进行一下软链接:

1
$ sudo ln -s /usr/bin/python3 /usr/bin/python

接下来,创建一个新的工程目录 project,首先需要在根目录下创建 WORKSPACE 文件,包含有 WORKSPACE 文件的目录将会被 Bazel 视为根目录。为了能够在这个工作区中使用 Python 构建规则,需要添加 Python 构建的规则到 WORKSPACE

1
2
3
4
5
6
7
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "rules_python",
    url = "https://github.com/bazelbuild/rules_python/releases/download/0.0.2/rules_python-0.0.2.tar.gz",
    strip_prefix = "rules_python-0.0.2",
    sha256 = "b5668cde8bb6e3515057ef465a35ad712214962f0b3a314e551204266c7be90c",
)

在根目录下创建 hello/main.py 文件:

1
2
if __name__ == "__main__":
    print("Hello World")

之后在 hello 下添加 BUILD 文件:

1
2
3
4
5
6
load("@rules_python//python:defs.bzl", "py_binary")

py_binary(
  name = "main",
  srcs = ["main.py"],
)

同样,运行 $ bazel run //hello:main,便可以执行 main.py。如果脚本需要参数的话,可以在中间用 -- 分隔,并紧跟在后面指定参数: $ bazel run //hello:main -- <Arguments>

远程执行

远程执行也是 Bazel 的一个亮点。远程执行的好处是可以利用云平台的多个机器进行分布式构建,并且缓存编译,这样可以大大加快构建速度。关于远程执行,具体参考官方的 Remote execution overview。好像要自己搭。

参考:

加载评论