本页介绍了 Bazel 的两个可见性系统: 目标可见性和加载可见性。
这两种类型的可见性都有助于其他开发者区分库的公共 API 和实现细节,并有助于在工作区不断扩大的情况下强制执行结构。您还可以在弃用公共 API 时使用可见性,以允许当前用户使用,同时拒绝新用户使用。
目标可见性
目标可见性 控制谁可以依赖于您的目标,也就是说,谁可以在 deps 等属性中使用目标的标签。
如果目标 A 和目标 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`
BUILD文件中package语句的default_visibility实参的值(如果存在此类声明);否则//visibility:private。
最佳实践: 避免将 default_visibility 设置为 public。这对于原型设计或小型代码库可能很方便,但随着代码库的增大,意外创建公共目标的风险也会增加。最好明确说明哪些目标是软件包公共接口的一部分。
示例
文件 //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 不会强制执行
config_setting目标的可见性,这些目标
在 select()的键中引用。有两个标志可用于移除此旧版行为:
--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,则此标志不起作用。
避免依赖旧版行为。如果软件包尚未指定合适的 default_visibility,则任何旨在在当前软件包之外使用的 config_setting 都应具有明确的 visibility。
软件包组目标可见性
package_group 目标没有 visibility 属性。它们始终公开可见。
隐式依赖项的可见性
某些规则具有隐式依赖项,即未在 BUILD 文件中明确说明但每个规则实例都固有的依赖项。例如,cc_library 规则可能会从其每个规则目标创建一个隐式依赖项,指向表示 C++ 编译器的可执行目标。
系统会根据包含定义规则(或切面)的 .bzl 文件的软件包检查此类隐式依赖项的可见性。在我们的示例中,只要 C++ 编译器与 cc_library 规则的定义位于同一软件包中,它就可以是私有的。作为回退,如果隐式依赖项在定义中不可见,则系统会根据 cc_library 目标检查它。
您可以通过停用
--incompatible_visibility_private_attributes_at_definition来更改此行为。
停用后,隐式依赖项将像任何其他依赖项一样处理。
这意味着,被依赖的目标(例如我们的 C++ 编译器)必须对规则的每个实例可见。实际上,这通常意味着目标必须具有公开可见性。
如果您想将规则的使用限制为某些软件包,请改用 加载可见性。
加载可见性
加载可见性 用于控制是否可以从当前软件包之外的其他 BUILD 或 .bzl 文件加载 .bzl 文件。
与目标可见性保护由目标封装的源代码的方式相同,加载可见性保护由 .bzl 文件封装的构建逻辑。例如,BUILD 文件作者可能希望将一些重复的目标定义分解为 .bzl 文件中的宏。如果没有加载可见性的保护,他们可能会发现自己的宏被同一工作区中的其他协作者重复使用,因此修改宏会破坏其他团队的构建。
请注意,.bzl 文件可能具有也可能没有对应的源文件目标。
如果有,则无法保证加载可见性和目标可见性一致。也就是说,同一个 BUILD 文件可能能够加载
.bzl 文件,但无法在 srcs 的 filegroup 中列出该文件,
反之亦然。这有时可能会给希望将 .bzl 文件用作源代码的规则带来问题,例如用于生成文档或进行测试。
对于原型设计,您可以通过设置 --check_bzl_visibility=false 来停用加载可见性强制执行。与 --check_visibility=false 一样,对于提交的代码,不应执行此操作。
加载可见性自 Bazel 6.0 起可用。
声明加载可见性
如需设置 .bzl 文件的加载可见性,请从该文件中调用
visibility() 函数。visibility() 的实参是软件包规范列表,就像
packages 的
package_group 属性一样。不过,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 文件可能需要对由多个较小的许可名单组成的许可名单可见。这类似于 a
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 文件中,这两种声明都可以引用该文件。根据上面分解可见性
中的示例,您可以编写:
# //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 Buildifier lint
如果用户从名为 internal
或 private 的目录加载文件,而用户的文件本身不在该目录的父级目录下,则 Buildifier lint
会提供警告。此 lint 早于加载可见性功能,并且在 .bzl 文件声明可见性的工作区中是不必要的。