Bazel 使用
- created: 2024-12-20T15:39+08:00
[toc]
总结
- MODULE.bazel 用于定义项目的直接依赖和自己要发布的版本
- BUILD 文件用于标识文件夹是一个 package,并需要在其中书写 build rules,语法类似 Python
- 每一个 rule 产生的实例叫做 target
- target 可以通过绝对路径索引,路径形式如:
//<package-folder>:<rule-name>
- rule 有
cc_binary()
用于编译可执行文件、cc_library()
用于编译库 - 通过 WORKSPACE 文件管理依赖是历史遗留,里面要手动 load 直接依赖和间接依赖,
一些发行的 module 会把这些 load 封装成一个函数并调用
单 package 单文件:用 cc_binary
首先要知道的就是 WORKSPACE 和 package 的概念1:
Before you can build a project, you need to set up its workspace.
A workspace is a directory that holds your project's source files and Bazel's build outputs.
It also contains these significant files:
- The MODULE.bazel file, which identifies the directory and its contents as a Bazel workspace and lives at the root of the project's directory structure.
It's also where you specify your external dependencies.- One or more BUILD files, which tell Bazel how to build different parts of the project.
A directory within the workspace that contains a BUILD file is a package.
简单来说,就是:
- MODULE.bazel 告诉 bazel 这个文件夹是一个 workspace;
- BUILD 告诉 bazel 这个文件夹是一个 package
single-package-to-executable tree .
.
├── MODULE.bazel
└── hello-world
├── BUILD
└── main.cc
BUILD 文件内容:
cc_binary(
name="build-main",
srcs=["main.cc"],
)
target 是 rule 的实例,像 cc_binary
这个 rule 指定编一个二进制可执行文件,这个 rule 的名字叫做 build-main
。
要触发编译,就是在指定 target,target 对应的 rule 路径为://<package-path>:<target-name>
bazel build //hello-world:build-main
单 package 多文件:依赖用 deps
和 cc_library
样例见 ./single-package-multi-files-to-executable
在 cc_library
rule 中需要通过 hdrs
来指定头文件。
这样的好处是可以增量编译。
single-package-multi-file-to-executable git:(main) ✗ tree .
.
├── MODULE.bazel
└── hello-world
├── BUILD
├── echo.cc
├── echo.h
└── main.cc
main.cc
中需要通过绝对路径引用头文件
#include <iostream>
#include "hello-world/echo.h" // <-- look here
using namespace std;
int main() {
std::cout << "hello world" << std::endl;
echo("echo hello world");
return 0;
}
多 package 编译可执行文件
样例见 ./multi-packages-to-executable
.
├── MODULE.bazel
├── calculator
│ ├── BUILD
│ ├── echo.cc
│ ├── echo.h
│ └── main.cc
└── math
├── BUILD
├── math.cc
└── math.h
math package 要编译成 library
, 并且需要通过 visibility
字段对外暴露:
cc_library(
name = "math",
srcs = ["math.cc"],
hdrs = ["math.h"],
visibility = ["//calculator:__pkg__"],
)
calculator package 要用 cc_binary
rule 编译, 同时需要指定 deps 为外部的 package,也是用绝对路径
cc_library (
name = "echo",
srcs = ["echo.cc"],
hdrs = ["echo.h"],
)
cc_binary(
name = "calculator",
srcs = ["main.cc"],
deps = [
":echo",
"//math:math"
],
)
编译指令:
bazel build //calculator:main
make system 要面临的问题
在介绍 Bazel 如何管理外部依赖前,我们需要了解做一套 make system 要面临哪些问题。
从需求的角度来说,比如我要发布一个 app,它依赖于 Lib A 和 Lib B,
这很简单,我只需要在 app 中声明使用 A 和 B,
然后 make system 自动帮我下载好 A 和 B,我就可以使用了。
假设 make system 需要一个叫做 Module 的文件来分析导入和导出依赖,我们 app 的 Module 文件内容如下:
export(app)
import(A)
import(B)
当版本管理出现后,make system 就犯难了。 如果,我们要 A@1.0.0 和 B@1.0.0,他们都依赖于 C,但是
A@1.0.0 -> C@1.0.0
B@1.0.0 -> C@2.0.0
这个时候有一种非常麻烦的办法,就是我手动告诉 make system,我要 C 的哪个版本,然后 A 和 B 都用对应版本: 那我们 app 的 Module 就会变成这样:
export(app, 1.0.0)
import(C, 1.0.0)
import(A, 1.0.0)
import(B, 1.0.0)
长此以往,如果依赖越来越多,就要指定跟多的依赖,这不是一个好办法。
要是我们只需要指定 app 直接的依赖 A 和 B,让 make system 自动选择可以的 C 就好了。
WORKSPACE
bazel 传统的依赖指定方式是通过 WORKSPACE
或者 WORKSPACE.bazel
文件,我们称为 WORKSPACE 方式,
它就是用上面最麻烦的、需要把间接依赖、直接依赖一个个指定出来2。
不仅如此,这种方式的语法也很恶心:
- 它连
import
函数都不是自带的,需要通过load
函数加载 import
函数不指定版本而是需要指定下载的 url 和 sha256
这个例子来自 bazel-build/examples
假如我们要构建一个 app,它依赖于 glog,但是 glog 还依赖于 gflags,
下面就是它的 WORKSPACE file,注意语法类似于 python:
# https://github.com/bazelbuild/examples/blob/main/bzlmod/01-depend_on_bazel_module/WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "com_github_google_glog",
sha256 = "eede71f28371bf39aa69b45de23b329d37214016e2055269b3b5e7cfd40b59f5",
strip_prefix = "glog-0.5.0",
urls = ["https://github.com/google/glog/archive/refs/tags/v0.5.0.tar.gz"],
)
# We have to define gflags as well because it's a dependency of glog.
http_archive(
name = "com_github_gflags_gflags",
sha256 = "34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf",
strip_prefix = "gflags-2.2.2",
urls = ["https://github.com/gflags/gflags/archive/refs/tags/v2.2.2.tar.gz"],
)
不过 BUILD 文件就很简单:
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = ["@com_github_google_glog//:glog"],
)
bzlmod
bzlmod 是目前(2024-12-20)最新的管理依赖的方式2,只需要指定直接依赖就好了。
每一个可以 export 和 import 的代码单元叫做「module」
# https://github.com/bazelbuild/examples/blob/main/bzlmod/01-depend_on_bazel_module/MODULE.bazel
module(
name = "example",
version = "0.0.1",
)
# 1. The metadata of glog is fetched from the BCR, including its dependencies (gflags).
# 2. The `repo_name` attribute allows users to reference this dependency via the `com_github_google_glog` repo name.
bazel_dep(name = "glog", version = "0.5.0", repo_name = "com_github_google_glog")
这个 bazel 起名字也是够奇怪的,python 是是单文件叫做 module,允许文件夹变成 package 和 sub-package
bazel 这儿 package 概念没变,整个发布出去的东西叫做 module。
1. Bazel Tutorial: Build a C++ Project ↩
2. External dependencies overview | Bazel ↩