Bazel 项目
Bazel 项目是源文件和构建文件的集合,这些文件用于描述以下各项:
- 如何构建工件(例如二进制文件或数据文件)及其依赖项。
- 如何运行特定命令,即脚本或可执行文件。run
- 如何测试上述 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
来存储构建指令。
不过,Bazel 还需要在 Windows 和 MacOS 上运行,因为 Windows 和 MacOS 的文件系统不区分大小写,并且许多 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
与构建机器上位于 /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
属性不应包含目录分隔符,除非在极少数情况下,以避免与源混淆。对于来自其他构建系统的开发者,这可能会使开发者感到困惑,这些系统可区分其构建图中的项目类型(例如,
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 混淆
规范代码库名称
从 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