Bazel 项目
Bazel 项目是一组源文件和构建文件,用于描述:
- 如何构建工件(例如二进制文件或数据文件)及其依赖项。
- 如何运行特定命令,例如脚本或可执行文件。
- 如何测试上述 build 工件。
Bazel 项目由顶级目录进行具体化,其内容遵循特定布局和惯例。
Bazel 工作区
Bazel 工作区是一个目录树,其中包含顶级 WORKSPACE.bazel
1 文件。这定义了一系列源代码和相关 build 文件的根目录。Bazel 项目可以使用多个工作区:
项目的根目录称为根 工作区,因此必须包含
WORKSPACE.bazel
文件。项目还可以引用其他工作区(称为外部 代码库),这些工作区对应于第三方项目依赖项。
例如:
/home/user/project/
WORKSPACE.bazel
src/
BUILD.bazel
extra/
extra.cc
lib/
BUILD.bazel
foo.cc
foo.h
main.cc
目录 /home/user/project
是根 Bazel 工作区,其中包含所有文件。
WORKSPACE.bazel
文件可以为空,但也可以包含引用其他工作区的指令,如后文所述。
Bazel 软件包
在工作区中,包含 BUILD.bazel
2 文件的目录定义了一个软件包,它是 Bazel 知晓的一组源文件和项的边界。
例如,以下文件布局:
/home/user/project/
WORKSPACE.bazel
BUILD.bazel
main.cc
定义一个位于 /home/user/project
的根工作区,其中包含一个顶级软件包,该软件包包含 BUILD.bazel
和 main.cc
文件。
BUILD.bazel
文件还可以包含用于定义命名项(例如目标、配置条件等)的说明,这些项在技术上也属于该软件包。
单个工作区中可以存在多个软件包,并且每个文件只能属于一个软件包。例如,使用以下文件布局:
/home/user/project/
WORKSPACE.bazel
BUILD.bazel
main.cc
lib/
BUILD.bazel
foo.cc
根工作区包含两个不同的软件包:
顶级软件包,其中仍包含文件
BUILD.bazel
和main.cc
(相对于根工作区目录)。第二个软件包,其中包含
lib/BUILD.bazel
和lib/foo.cc
文件。
请注意,/home/user/project/lib/foo.cc
中的文件仅属于第二个软件包,而非第一个软件包。这是因为软件包 边界 绝不会 重叠。
BUILD.bazel
对BUILD
Bazel 源自 Google 的 Blaze,后者仅适用于区分大小写的文件系统的 Linux。Blaze 仅使用文件名 BUILD
来存储 build 指令。
不过,Bazel 还需要在具有不区分大小写文件系统的 Windows 上运行,而许多 Google 或非 Google 项目已在使用名为“build
”的目录,这会与此类系统上名为“BUILD
”的文件冲突。
为解决此问题,Bazel 使用 BUILD.bazel
和 WORKSPACE.bazel
作为默认文件名,同时仍支持将 BUILD
和 WORKSPACE
用作后备。
工作区指令
根 WORKSPACE.bazel
可以为空,也可以包含引用其他 Bazel 工作区(称为外部代码库)的指令。这些指令始终会为代码库指定名称。例如:
local_repository(
name = "my_ssl",
path = "/home/user/src/openssl-bazel",
)
将名称 my_ssl
与位于 build 机上的 /home/user/src/openssl-bazel
的工作区相关联。此目录还必须包含 WORKSPACE.bazel
(或 WORKSPACE
)文件。
代码库只是一个带有名称的外部 Bazel 工作区。此名称是项目本地的名称,稍后可用于引用外部工作区的项目(见下文)。
Bazel 还支持其他指令,用于从网络下载代码库,甚至以编程方式生成其内容。
Bazel 标签
Bazel 标签是对源文件和 BUILD.bazel
文件中定义的项的字符串引用。其一般格式为:
@<repository_name>//<package_name>:<target_name>
其中:
@<repository_name>//
用于指定命名 Bazel 工作区的目录。为方便起见,对于当前工作区(包含当前
BUILD.bazel
文件的工作区),可以将其简写为//
。另请注意,即使在外部代码库中使用,@//
也用于指定项目的根工作区。<package_name>
是相对于工作区目录的软件包目录路径。例如,在标签//src:main.cc
或//src/lib:foo
中,软件包名称分别为src
和src/lib
。此值可以为空,例如
//:BUILD.bazel
指向当前工作区的顶级目录中的 build 文件。对于源文件,
<target_name>
是相对于其父级软件包目录的文件路径,并且可能包含子目录部分。例如,对于//src:main.cc
或//src:extra/extra.cc
,目标名称分别为main.cc
和extra/extra.cc
。对于其他项,
<target_name>
对应于BUILD.bazel
文件中定义的项(build 工件、build 设置、配置条件等)。按照惯例,除极少数情况外,其
name
属性不应包含目录分隔符,以免与来源混淆。对于来自其他 build 系统的开发者来说,这可能会造成困惑,因为其他 build 系统会区分 build 图中的项类型(例如,
GN
使用“目标”“配置”“工具链”和“池”来指定不同的内容)。
还支持使用缩写标签表达式:
如果标签以代码库名称开头且不包含冒号,则表示它是软件包路径,指向同名项。例如,
//src/foo
等同于//src/foo:foo
。如果标签以冒号开头,则表示相对于当前软件包的名称。例如,
src/foo/BUILD.bazel
中显示的“:bar
”和“:extra/bar.cc
”分别等同于//src/foo:bar
和//src/foo:extra/bar.cc
。如果标签没有代码库名称和英文冒号,则始终是相对于当前软件包的名称,即使包含目录分隔符也是如此。例如,
src/foo/BUILD.bazel
中的“bar/bar.cc
”始终是指//src/foo:bar/bar.cc
。请注意,这与
//src/foo/bar:bar.cc
不同
相对标签和软件包所有权
由于每个源文件只能属于一个软件包,因此相对标签可能会无效。例如,在如下所示的项目中:
/home/user/project/
WORKSPACE.bazel
src/
BUILD.bazel
main.cc
extra/
extra.cc
lib/
BUILD.bazel
foo.cc
foo.h
foo.cc
文件属于软件包 src/lib
,因此其标签必须为 //src/lib:foo.cc
。
在 src/BUILD.bazel
中使用 src:lib/foo.cc
等标签是错误的:
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"lib/foo.cc", # Error: Label '//src:lib/foo.cc' is invalid because 'src/lib' is a subpackage
"lib/foo.h" # Error: Label '//src:lib/foo.h' is invalid because 'src/lib' is a subpackage
"main.cc",
],
)
从其他软件包访问源文件
默认情况下,其他软件包无法访问给定软件包的源文件,并且相对软件包标签无效,如下所示:
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"lib:foo.cc", # Error: invalid label 'lib:foo.cc': absolute label must begin with '@' or '//'
"lib:foo.h" # Error: invalid label 'lib:foo.h': absolute label must begin with '@' or '//'
"main.cc",
],
)
即使使用正确的绝对标签,也会发生错误:
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"//src/lib:foo.cc", # Error: no such target '//src/lib:foo.cc': target 'foo.h' not declared in package 'src/lib'
"//src/lib:foo.h" # Error: no such target '//src/lib:foo.h': target 'foo.h' not declared in package 'src/lib'
"main.cc",
],
)
export_files()
可以授予对跨软件包边界的文件的直接访问权限:
# From src/lib/BUILD.bazel
export_files([
"foo.cc" ,
"foo.h" ,
])
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"//src/lib:foo.cc", # OK
"//src/lib:foo.h" # OK
"main.cc",
],
)
从其他软件包访问目标
在 BUILD.bazel
文件中定义的标记项(非源文件)无需导出,但其 visibility
属性必须允许在其自身软件包之外使用:
# From src/lib/BUILD.bazel
cc_library(
name = " lib" ,
srcs = [ " foo.cc" ],
hdrs = [ " foo.h" ],
visibility = [ " //visibility:public" ], # Anyone can reference this directly!
)
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"main.cc",
],
deps = [ "lib" ], # OK!
)
默认情况下,项仅对同一软件包中的其他项可见。您可以使用 package()
指令更改软件包中定义的所有项的默认公开范围,从而更改此设置:
# From src/lib/BUILD.bazel
# Ensure that all items defined in this file are visible to anyone
package(default_visibility = ["//visibility:public"])
cc_library(
name = " lib" ,
srcs = [ "foo.cc" ],
hdrs = [ "foo.h" ],
)
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"main.cc",
],
deps = [ "lib" ], # OK!
)
关于虚拟软件包的警告
避免在项目中创建以下名称的顶级目录:
conditions
command_line_option
external
visibility
这是因为 Bazel 在 BUILD.bazel
文件中的标签中使用了多个硬编码的“虚拟软件包”。例如:
//visibility:public
//conditions:default
//command_line_option:copt
external
的情况略有不同:它不会显示在 BUILD.bazel
文件中,而是在内部用于管理外部代码库。这会导致 Bazel 在用作项目目录时感到困惑
规范代码库名称
从 Bazel 6.0 开始,标签中的代码库名称也可以以 @@
开头。
启用可选的 BzlMod 功能后,这些标签会用作外部代码库的替代但唯一标签名称,这在项目中使用复杂的传递依赖项树时非常重要。
例如,@@com_acme_anvil.1.0.3
可以是工作区目录的规范名称,在项目自己的 BUILD.bazel
文件中由 @anvil
标识,在外部代码库(例如 @foo//:BUILD.bazel
中)中则由 @acme_anvil
标识。这三个标签都指同一目录的内容。
规范代码库名称不会显示在 BUILD.bazel
文件中,但会在分析阶段(执行查看标签值的 Starlark 函数时)或查看 Bazel 查询的结果时显示。
Bazel 扩展 (.bzl
) 文件
扩展程序文件包含可导入到其他几个文件中的额外定义:
它们的名称始终以
.bzl
文件扩展名结尾。它们必须属于 Bazel 软件包,因此可以通过标签进行标识。例如
//bazel_utils:defs.bzl
。它们采用 Starlark 语言编写,并且应遵循特定准则。
它们是唯一可定义 Starlark 函数的位置! 换句话说,您无法在
BUILD.bazel
文件中定义函数!它们始终只会评估一次,即使多次导入,它们定义的变量和函数也会被记录为常量。
您可以使用
load()
语句从其他文件导入它们。
例如:
以下内容来自
$PROJECT/my_definitions.bzl
:# The official release number release_version = "1.0.0"
以下内容来自
$PROJECT/BUILD.bazel
:# Import the value of `release_version` from my_definitions.bzl load("//:my_definitions.bzl", "release_version") # Compile C++ executable, hard-coding its version number with a macro. cc_binary( name = "my_program", defines = [ "RELEASE_VERSION=" + release_version ], sources = [ … ], )
load()
语句具有特殊的语义:
其第一个参数必须是
.bzl
文件的标签字符串(例如"//src:definitions.bzl"
)。其他参数用于命名导入的常量或函数:
如果参数是字符串,则必须是
.bzl
文件定义的导入符号的名称。例如:load("//src:defs.bzl", "my_var", "my_func")
如果参数是变量赋值,则会为导入的符号定义一个本地别名。E.g.:
load("//src:defs.bzl", "my_var", func = "my_func")
不支持通配符:所有导入的常量和函数都必须明确命名。
当
load()
出现在.bzl
文件中时,系统绝不会记录导入的符号。同样,名称以下划线开头的符号(例如
_foo
)永远不会被记录,也无法导入。也就是说,它们仅对定义它们的.bzl
文件可见。
有时,.bzl
文件需要从另一个文件导入符号,并使用相同的名称重新导出该符号。这需要使用别名,例如:
# From //src:utils.bzl
# Import "my_vars" from defs.bzl as '_my_var'.
load("//src:defs.bzl", _my_var = "my_var")
# Define my_var in the current scope as a copy of _my_var
# This symbol and its value will be recorded, and available for import
# to any other file that loads //src:utils.bzl.
my_var = _my_var