公开范围

报告问题 查看源代码

本页面介绍了 Bazel 的两个可见性系统:目标可见性加载可见性

这两种类型的可见性都有助于其他开发者区分库的公共 API 及其实现详情,并且有助于在工作区不断扩大时强制执行结构。在弃用公共 API 时,您还可以使用可见性,以允许当前用户同时拒绝新用户。

目标可见性

目标可见性控制谁可能依赖于您的目标,即谁可以在 deps 等属性中使用目标的标签。

如果目标 A 位于同一软件包中,或者 A 授予对 B 软件包的可见性,则该目标 A 对目标 B 可见。因此,软件包是决定是否允许访问的粒度单位。如果 B 依赖于 A,但 BA 不可见,则表示对构建 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:mytargetvisibility 设置为 [":__subpackages__", "//tests:__pkg__"],则 //some/package/... 源代码树中的任何目标以及在 //tests/BUILD 中定义的目标都可以使用它,但 //tests/integration/BUILD 中定义的目标无法使用它。

最佳实践:如需使多个目标对同一组软件包可见,请使用 package_group,而不是在每个目标的 visibility 属性中重复列出相应列表。这样可提高可读性,并防止列表不同步。

规则目标可见性

规则目标的可见性为:

  1. visibility 属性的值(如果已设置);否则

  2. 目标的 BUILD 文件中 package 语句的 default_visibility 参数的值(如果存在此类声明);否则

  3. //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 来明确设置源文件目标的可见性。如果没有 visibility 参数传递给 exports_files,则会公开可见性。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 可为这些目标启用可见性检查。为协助迁移,它还会导致任何未指定 visibilityconfig_setting 被视为公开(无论软件包级 default_visibility 如何)。

  • --incompatible_config_setting_private_default_visibility 会导致未指定 visibilityconfig_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 文件中的宏。如果不保护负载可见性,他们可能会发现同一工作区中的其他协作者会重复使用其宏,因此修改宏会破坏其他团队的 build。

请注意,.bzl 文件不一定有对应的源文件目标。如果包含,无法保证负载可见性和目标可见性一致。也就是说,同一个 BUILD 文件或许能够加载 .bzl 文件,但不会将其列在 filegroupsrcs 中,反之亦然。对于希望将 .bzl 文件用作源代码的规则(例如用于生成或测试文档),这有时可能会导致出现问题。

对于原型设计,您可以通过设置 --check_bzl_visibility=false 来停用加载可见性强制执行。与 --check_visibility=false 一样,不应对已提交的代码执行此操作。

从 Bazel 6.0 开始,可以使用加载可见性。

声明加载可见性

如需设置 .bzl 文件的加载可见性,请从文件中调用 visibility() 函数。visibility() 的参数是一个软件包规范列表,就像 package_grouppackages 属性一样。不过,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 文件中,因为这两种声明都可能会引用该文件。根据上面的因式分解可见性中的示例,您可以编写以下代码:

# //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

如果用户从名为 internalprivate 的目录加载文件,而用户的文件本身并不位于该目录的父目录下,则 Buildifier lint 会发出警告。此 lint 早于加载可见性功能,在 .bzl 文件声明可见性的工作区中无需此 lint。