顯示設定

7.3 · 7.2 · 7.1 · 7.0 · 6.5

本頁面將介紹 Bazel 的兩個可見度系統:目標可見度載入可見度

這兩種瀏覽權限可協助其他開發人員區分程式庫的公用 API 與實作詳細資料,並有助於隨著工作區擴增來強制執行結構。淘汰公用 API 時,您也可以使用瀏覽權限,在拒絕新使用者時允許現有使用者。

指定可視度

目標對象瀏覽權限可控管哪些使用者可能會依附目標對象,也就是哪些使用者可能會在 deps 等屬性中使用目標對象的標籤。

如果目標 B 屬於同一套件,或者A允許查看B套件,目標 A 就可以看到。因此,套件是決定是否允許存取權的精細單位。如果 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: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++ 編譯器的可執行目標。

目前,為了方便查看,這些隱含的依附元件會與其他依附元件一樣受到處理。也就是說,每個規則的例項都必須能看到所依附的目標 (例如 C++ 編譯器)。在實務上,通常代表目標必須具有公開瀏覽權限。

如要變更這項行為,請設定 --incompatible_visibility_private_attributes_at_definition。啟用後,問題中的目標只需對宣告為隱含依附元件的規則可見。也就是說,該檔案必須對包含定義規則的 .bzl 檔案可見。在我們的範例中,C++ 編譯器可設為私密,只要和 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() 的呼叫只能發生一次,最好在 load() 陳述式之後。

與目標瀏覽權限不同,預設的載入瀏覽權限一律為公開。不呼叫 visibility() 的檔案一律可從工作區的任何位置載入。建議您在任何新 .bzl 檔案的頂端新增 visibility("private"),除非該檔案是專門用於套件外部。

範例

# //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 檔案宣告可見度的工作區中,這項 Lint 就沒有必要。