本页面介绍了 Bazel 的两个可见性系统:目标可见性和负载可见性。
这两种类型的可见性都有助于其他开发者区分库的公共 API 及其实现细节,并随着工作区的增长帮助执行结构。在弃用公共 API 时,您还可以使用可见性,以允许当前用户拒绝新用户。
目标公开范围
目标可见性控制哪些用户可能会依赖您的目标,即谁可以在 deps
等属性中使用目标的标签。
如果目标 B
位于同一软件包中,或者 A
向 B
的软件包授予可见性,则目标 A
对目标 B
可见。因此,软件包是决定是否允许访问的粒度单位。如果 B
依赖于 A
,但 A
对 B
不可见,则任何尝试构建 B
的操作都会在分析期间失败。
请注意,向软件包授予可见性本身并不会为其子软件包授予可见性。如需详细了解软件包和子软件包,请参阅概念和术语。
对于原型设计,您可以通过设置 --check_visibility=false
标志来停用目标可见性强制执行。不应针对已提交的代码中的生产使用情况执行此操作。
控制可见性的主要方法是使用规则目标的 visibility
特性。本部分介绍此属性的格式以及如何确定目标的可见性。
可见性规范
所有规则目标都有一个 visibility
属性,该属性带有一个标签列表。每个标签都具有以下形式之一。除最后一个形式之外,这些占位符只是与任何实际目标不对应的语法占位符。
"//visibility:public"
:授予对所有软件包的访问权限。(不得与任何其他规范结合使用。)"//visibility:private"
:不授予任何其他访问权限;只有此软件包中的目标才能使用此目标。(不得与任何其他规范结合使用。)"//foo/bar:__pkg__"
:授予对//foo/bar
(但不包括其子软件包)的访问权限。"//foo/bar:__subpackages__"
:授予对//foo/bar
及其所有直接和间接子软件包的访问权限。"//some_pkg:my_package_group"
:授予对指定package_group
中所有软件包的访问权限。- 软件包组使用不同的语法指定软件包。在软件包组中,
"//foo/bar:__pkg__"
和"//foo/bar:__subpackages__"
格式已分别替换为"//foo/bar"
和"//foo/bar/..."
。同样,"//visibility:public"
和"//visibility:private"
只是"public"
和"private"
。
- 软件包组使用不同的语法指定软件包。在软件包组中,
例如,如果 //some/package:mytarget
的 visibility
设置为 [":__subpackages__", "//tests:__pkg__"]
,则 //some/package/...
源代码树中的所有目标以及 //tests/BUILD
中定义的目标均可使用该配置,但 //tests/integration/BUILD
中定义的目标则不能使用它。
最佳做法:如需让同一组软件包对多个目标可见,请使用 package_group
,而不是在每个目标的 visibility
属性中重复列出该列表。这可以提高可读性,并防止列表不同步。
规则目标可见性
规则目标的公开范围为:
其
visibility
属性的值(如果已设置);否则目标的
BUILD
文件中package
语句的default_visibility
参数的值(如果存在此类声明);或者//visibility:private
。
最佳做法:避免将 default_visibility
设置为“公开”。它可能很适合进行原型设计或在小型代码库中使用,但随着代码库的增加,创建公共目标的风险会更大。最好明确哪些目标属于软件包的公共接口。
示例
文件 //frobber/bin/BUILD
:
# This target is visible to everyone
cc_binary(
name = "executable",
visibility = ["//visibility:public"],
deps = [":library"],
)
# This target is visible only to targets declared in the same package
cc_library(
name = "library",
# No visibility -- defaults to private since no
# package(default_visibility = ...) was used.
)
# This target is visible to targets in package //object and //noun
cc_library(
name = "subject",
visibility = [
"//noun:__pkg__",
"//object:__pkg__",
],
)
# See package group "//frobber:friends" (below) for who can
# access this target.
cc_library(
name = "thingy",
visibility = ["//frobber:friends"],
)
文件 //frobber/BUILD
:
# This is the package group declaration to which target
# //frobber/bin:thingy refers.
#
# Our friends are packages //frobber, //fribber and any
# subpackage of //fribber.
package_group(
name = "friends",
packages = [
"//fribber/...",
"//frobber",
],
)
生成的文件目标可见性
生成的文件目标的可见性与生成它的目标规则相同。
源文件目标可见性
您可以通过调用 exports_files
明确设置源文件目标的可见性。如果未向 exports_files
传递任何 visibility
参数,则该可见性会变为公开状态。exports_files
不能用于替换已生成文件的可见性。
对于未出现在 exports_files
调用中的源文件目标,可见性取决于标志 --incompatible_no_implicit_file_export
的值:
如果设置了此标记,则可见性不会公开。
否则,旧版行为适用:公开范围与
BUILD
文件的default_visibility
相同,如果未指定默认公开范围,则设为不公开。
避免依赖旧版行为。每当源文件目标需要非公开可见性时,始终编写 exports_files
声明。
最佳做法:尽可能建议您公开规则目标,而不是源文件。例如,不要对 .java
文件调用 exports_files
,而是将文件封装在非私有 java_library
目标中。通常,规则目标应仅直接引用位于同一软件包中的源文件。
示例
文件 //frobber/data/BUILD
:
exports_files(["readme.txt"])
文件 //frobber/bin/BUILD
:
cc_binary(
name = "my-program",
data = ["//frobber/data:readme.txt"],
)
配置设置公开范围
过去,Bazel 尚未对 select()
的键中引用的 config_setting
目标强制执行可见性。您可以通过以下两种标志移除此旧版行为:
--incompatible_enforce_config_setting_visibility
支持对这些目标进行可见性检查。为了协助迁移,对于没有指定visibility
的任何config_setting
,也会导致相应应用被视为公开(无论软件包级别default_visibility
为何)。--incompatible_config_setting_private_default_visibility
会使未指定visibility
的config_setting
遵守软件包的default_visibility
,并在私有可见性上回退,就像其他任何规则目标一样。如果未设置--incompatible_enforce_config_setting_visibility
,则为空操作。
避免依赖旧版行为。任何打算在当前软件包之外使用的 config_setting
都应具有明确的 visibility
,前提是该软件包尚未指定合适的 default_visibility
。
软件包组目标可见性
package_group
目标没有 visibility
属性。它们始终公开显示。
隐式依赖项的可见性
某些规则具有隐式依赖项 - 这些依赖项未在 BUILD
文件中拼写出来,但该规则的每个实例都是固有的依赖项。例如,cc_library
规则可以创建从其每个规则目标到代表 C++ 编译器的可执行目标的隐式依赖项。
目前,为清楚起见,系统会将这些隐式依赖项视为任何其他依赖项。这意味着,所依赖的目标(例如我们的 C++ 编译器)必须对规则的每个实例可见。在实践中,这通常意味着目标必须公开可见。
您可以通过设置 --incompatible_visibility_private_attributes_at_definition
来更改此行为。启用后,相关目标只需要对声明其隐式依赖项的规则可见。也就是说,必须使其对包含定义该规则的 .bzl
文件的软件包可见。在我们的示例中,只要 C++ 编译器与 cc_library
规则的定义位于同一软件包中,就可以是私有编译器。
加载可见性
加载可见性用于控制是否可以从当前软件包以外的其他 BUILD
或 .bzl
文件中加载 .bzl
文件。
与目标可见性可以保护由目标封装的源代码一样,负载可见性也可以保护由 .bzl
文件封装的构建逻辑。例如,BUILD
文件作者可能希望将一些重复的目标定义分解为 .bzl
文件的宏。如果没有保护负载可见性,他们可能会发现同一工作区中的其他协作者重复使用其宏,因此修改此宏会破坏其他团队的 build。
请注意,.bzl
文件不一定具有相应的源文件目标。
如果包含,则无法保证负载可见性和目标可见性一致。也就是说,同一个 BUILD
文件可能能够加载 .bzl
文件,但不会显示在 filegroup
的 srcs
中,反之亦然。有时,这可能会导致规则希望将 .bzl
文件用作源代码(例如,用于生成或测试文档)。
对于原型设计,您可以通过设置 --check_bzl_visibility=false
停用负载可见性强制执行。与 --check_visibility=false
一样,不应为提交的代码执行此操作。
负载可见性从 Bazel 6.0 开始提供。
声明加载可见性
如需设置 .bzl
文件的加载可见性,请在文件中调用 visibility()
函数。visibility()
的参数是一个软件包规范列表,就像 package_group
的 packages
属性一样。不过,visibility()
不接受否定软件包规范。
对每个文件只能调用 visibility()
一次,且只能在顶层(而不是函数内)调用,最好在 load()
语句之后执行。
与目标可见性不同,默认加载可见性始终公开。不调用 visibility()
的文件始终可从工作区中的任何位置加载。最好将 visibility("private")
添加到任何并非专门在软件包之外使用的新 .bzl
文件的顶部。
示例
# //mylib/internal_defs.bzl
# Available to subpackages and to mylib's tests.
visibility(["//mylib/...", "//tests/mylib/..."])
def helper(...):
...
# //mylib/rules.bzl
load(":internal_defs.bzl", "helper")
# Set visibility explicitly, even though public is the default.
# Note the [] can be omitted when there's only one entry.
visibility("public")
myrule = rule(
...
)
# //someclient/BUILD
load("//mylib:rules.bzl", "myrule") # ok
load("//mylib:internal_defs.bzl", "helper") # error
...
加载可见性做法
本部分介绍了有关管理负载可见性声明的提示。
考虑因素
如果多个 .bzl
文件应具有相同的公开范围,将软件包规范分解到通用列表中会很有帮助。例如:
# //mylib/internal_defs.bzl
visibility("private")
clients = [
"//foo",
"//bar/baz/...",
...
]
# //mylib/feature_A.bzl
load(":internal_defs.bzl", "clients")
visibility(clients)
...
# //mylib/feature_B.bzl
load(":internal_defs.bzl", "clients")
visibility(clients)
...
这有助于防止各种 .bzl
文件之间出现意外的偏差。当 clients
列表较大时,可读性更高。
可视性
有时,一个 .bzl
文件可能对由多个较小的许可名单构成的许可名单可见。这类似于 package_group
如何通过其 includes
属性整合其他 package_group
。
假设您要弃用一个广泛使用的宏。您希望它仅对现有用户以及您自己的团队拥有的软件包可见。您可以这样写:
# //mylib/macros.bzl
load(":internal_defs.bzl", "our_packages")
load("//some_big_client:defs.bzl", "their_remaining_uses")
# List concatenation. Duplicates are fine.
visibility(our_packages + their_remaining_uses)
删除软件包组
与目标可见性不同,您无法根据 package_group
定义负载可见性。如果要将同一许可名单同时用于目标公开范围和负载可见性,最好将软件包规范列表移到 .bzl 文件中,因为这两种声明可能会引用 .bzl 文件。基于上述考虑因素中的示例,您可以这样编写:
# //mylib/BUILD
load(":internal_defs", "clients")
package_group(
name = "my_pkg_grp",
packages = clients,
)
仅当列表不包含任何负软件包规范时,这种方法才有效。
保护单个符号
无法从其他文件加载名称以下划线开头的任何 Starlark 符号。这样可以轻松创建私有符号,但不允许与一组有限的可信文件共享这些符号。另一方面,负载可见性可让您控制其他哪些软件包可能会看到您的 .bzl file
,但不允许阻止加载任何无下划线的符号。
幸运的是,您可以将这两项功能结合使用,实现精细控制。
# //mylib/internal_defs.bzl
# Can't be public, because internal_helper shouldn't be exposed to the world.
visibility("private")
# Can't be underscore-prefixed, because this is
# needed by other .bzl files in mylib.
def internal_helper(...):
...
def public_util(...):
...
# //mylib/defs.bzl
load(":internal_defs", "internal_helper", _public_util="public_util")
visibility("public")
# internal_helper, as a loaded symbol, is available for use in this file but
# can't be imported by clients who load this file.
...
# Re-export public_util from this file by assigning it to a global variable.
# We needed to import it under a different name ("_public_util") in order for
# this assignment to be legal.
public_util = _public_util
bzl-Visibility 构建器 lint
有一个 Builderifier lint,它会在用户从名为 internal
或 private
的目录加载文件时,在该用户的文件本身不位于其父目录下时发出警告。此 lint 早于负载可见性功能,并且在 .bzl
文件声明可见性的工作区中是不必要的。