顯示設定

回報問題 查看原始碼

本頁面說明 Bazel 的兩種瀏覽權限系統:目標瀏覽權限負載可見度

這兩種瀏覽權限都可協助其他開發人員區分程式庫的公用 API 及其實作詳細資料,協助隨著工作區的成長,強制實行結構。淘汰公用 API 時也可以使用瀏覽權限,允許目前的使用者同時拒絕新使用者。

目標瀏覽權限

「目標瀏覽權限」可控管可能取決於目標的使用者,也就是誰可能會在 deps 這類屬性中使用您目標標籤。

如果目標 A 位於相同套件,或是 A 授予 B 套件的瀏覽權限,則目標 B 就可以看到目標 A。因此,套件是用來決定是否允許存取的精細程度單位。如果 B 依附 A,但 B 無法查看 A,則在分析期間,任何嘗試建構 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 屬性中重複列出清單。這會提高可讀性,並避免清單無法同步。

最佳做法:將瀏覽權限授予其他團隊的專案時,最好使用 __subpackages__ 而非 __pkg__,以免隨著專案不斷演進並加入新的子套件,從而避免無需要的可見度。

規則目標可見度

規則目標的瀏覽權限如下:

  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_library 目標中,而不是在 .java 檔案上呼叫 exports_files。一般而言,規則目標應該只直接參照位於相同套件中的來源檔案。

範例

檔案 //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 會使未指定 visibilityconfig_setting 會遵循套件的 default_visibility,並對私人瀏覽權限的備用,就像任何其他規則目標一樣。如未設定 --incompatible_enforce_config_setting_visibility,則為免人工管理。

避免依賴舊版行為。凡是要在目前套件外使用的 config_setting,如果套件尚未指定適當的 default_visibility,就應該包含明確的 visibility

套件群組目標瀏覽權限

package_group 目標缺少 visibility 屬性。這些內容一律會公開顯示。

隱含依附元件的瀏覽權限

有些規則具有「隱含依附元件」,這類依附元件雖然沒有在 BUILD 檔案中出現,但組成該規則的每個執行個體。舉例來說,cc_library 規則可能會從其每個規則目標,建立隱含依附元件至代表 C++ 編譯器的可執行目標。

系統會根據定義規則 (或切面) 的 .bzl 檔案,檢查這類隱含依附元件的瀏覽權限。在本範例中,只要 C++ 編譯器位於與 cc_library 規則定義相同的套件中,就能設為不公開。做為備用方案,如果定義未顯示隱含依附元件,則系統會根據 cc_library 目標進行檢查。

如果您想將規則限制在特定套件的使用,請改用載入瀏覽權限

載入瀏覽權限

「載入瀏覽權限」可控管 .bzl 檔案是否能從目前套件以外的其他 BUILD.bzl 檔案載入。

就像目標瀏覽權限可保護由目標封裝的原始碼一樣,載入瀏覽權限會保護由 .bzl 檔案封裝的建構邏輯。舉例來說,BUILD 檔案作者可能會想將某些重複的目標定義分解為 .bzl 檔案中的巨集。如果沒有保護負載的可視性,他們可能會發現同一個工作區中其他協作者重複使用其巨集,導致修改巨集會破壞其他團隊的建構作業。

請注意,.bzl 檔案不一定有對應的來源檔案目標。如果達到這個目標,則無法保證負載可見度和目標可見度會發生衝突。也就是說,同一個 BUILD 檔案可以載入 .bzl 檔案,但無法將其列在 filegroupsrcs 中,反之亦然。對於想將 .bzl 檔案做為原始碼使用 (例如產生或測試說明文件) 的規則有時可能會造成問題。

針對原型設計,您可以設定 --check_bzl_visibility=false 停用負載可見度的強制執行功能。和 --check_visibility=false 一樣,請不要為提交的程式碼進行此操作。

載入可視性自 Bazel 6.0 起。

宣告負載可見度

如要設定 .bzl 檔案的載入瀏覽權限,請從檔案呼叫 visibility() 函式。visibility() 的引數是套件規格的清單,與 package_grouppackages 屬性一樣。不過,visibility() 不接受負值套件規格。

在頂層 (而非函式中),對 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 Buildifier Lint

如果使用者的檔案本身並非位於該目錄的父項底下,那麼當使用者從名為 internalprivate 的目錄載入檔案時,Buildifier Lint 會顯示警告。這個 Lint 會優先採用載入瀏覽權限功能,且對於 .bzl 檔案宣告瀏覽權限的工作區而言不需要這項程式碼。