顯示設定

回報問題 查看來源 Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

本頁面將說明 Bazel 的兩種可見度系統:目標可見度載入可見度

這兩種可見度都有助於其他開發人員區分程式庫的公開 API 和實作詳細資料,並在工作區擴大時強制執行結構。您也可以在淘汰公開 API 時使用可見度,允許現有使用者存取,但拒絕新使用者存取。

目標曝光度

目標可見度可控管哪些人可依附目標,也就是哪些人可在 deps 等屬性中使用目標的標籤。如果目標違反其中一個依附元件的瀏覽權限,就會在分析階段建構失敗。

一般來說,目標 A 和目標 B 必須位於同一地點,或是 A 授予 B 位置資訊的存取權,B 才能看到 A。如果沒有符號巨集,「位置」一詞可簡化為「套件」;如要進一步瞭解符號巨集,請參閱下文

只要列出允許的套件,即可指定瀏覽權限。允許套件不一定代表同時允許子套件。如要進一步瞭解套件和子套件,請參閱「概念和術語」。

如要進行原型設計,您可以設定 --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__,以免專案演進及新增子封裝時,瀏覽權限不必要地變動。

規則目標顯示設定

系統會根據規則目標的 visibility 屬性 (或適當的預設值,如果沒有提供) 判斷目標的顯示設定,並附加目標的宣告位置。如果目標未在符號巨集中宣告,且套件指定了 default_visibility,則會使用這個預設值;對於所有其他套件和在符號巨集中宣告的目標,預設值為 ["//visibility:private"]

# //mypkg/BUILD

package(default_visibility = ["//friend:__pkg__"])

cc_library(
    name = "t1",
    ...
    # No visibility explicitly specified.
    # Effective visibility is ["//friend:__pkg__", "//mypkg:__pkg__"].
    # If no default_visibility were given in package(...), the visibility would
    # instead default to ["//visibility:private"], and the effective visibility
    # would be ["//mypkg:__pkg__"].
)

cc_library(
    name = "t2",
    ...
    visibility = [":clients"],
    # Effective visibility is ["//mypkg:clients, "//mypkg:__pkg__"], which will
    # expand to ["//another_friend:__subpackages__", "//mypkg:__pkg__"].
)

cc_library(
    name = "t3",
    ...
    visibility = ["//visibility:private"],
    # Effective visibility is ["//mypkg:__pkg__"]
)

package_group(
    name = "clients",
    packages = ["//another_friend/..."],
)

最佳做法:避免將 default_visibility 設為公開。這在原型設計或小型程式碼集可能很方便,但隨著程式碼集擴大,不慎建立公開目標的風險也會增加。最好明確指出哪些目標屬於套件的公開介面。

產生的檔案目標可視度

產生的檔案目標與產生該檔案的規則目標具有相同的顯示設定。

# //mypkg/BUILD

java_binary(
    name = "foo",
    ...
    visibility = ["//friend:__pkg__"],
)
# //friend/BUILD

some_rule(
    name = "bar",
    deps = [
        # Allowed directly by visibility of foo.
        "//mypkg:foo",
        # Also allowed. The java_binary's "_deploy.jar" implicit output file
        # target the same visibility as the rule target itself.
        "//mypkg:foo_deploy.jar",
    ]
    ...
)

來源檔案目標瀏覽權限

您可以使用 exports_files 明確宣告來源檔案目標,也可以在規則的標籤屬性中參照來源檔案名稱 (在符號巨集之外),隱含建立來源檔案目標。與規則目標相同,對 exports_files 的呼叫位置,或參照輸入檔案的 BUILD 檔案,一律會自動附加至檔案的可見度。

exports_files 宣告的檔案可透過該函式的 visibility 參數設定瀏覽權限。如果未提供這項參數,則顯示設定為公開。

如果檔案未顯示在 exports_files 的呼叫中,則可見度取決於 --incompatible_no_implicit_file_export 標記的值:

  • 如果標記為 true,則瀏覽權限為「私人」。

  • 否則,系統會套用舊版行為:瀏覽權限與 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 會導致 config_settings (未指定 visibility) 的 default_visibility 遵守套件的 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 目標。

如要將規則的使用限制在特定套件,請改用載入曝光度

可見度和符號巨集

本節說明可見度系統如何與符號巨集互動。

符號巨集中的位置

可見度系統的重要細節是我們如何判斷聲明的位置。如果目標未在符號巨集中宣告,則位置只是目標所在的套件,也就是 BUILD 檔案的套件。但如果是以符號巨集建立的目標,位置就是包含巨集定義 (my_macro = macro(...) 陳述式) 的 .bzl 檔案所在套件。在多個巢狀目標內建立目標時,系統一律會使用最內層符號巨集的定義。

系統會使用相同機制,判斷要檢查哪個位置的特定依附元件可視度。如果取用目標是在巨集中建立,我們會查看最內層巨集的定義,而不是取用目標所在的套件。

也就是說,在同一個套件中定義程式碼的所有巨集,都會自動成為彼此的「好友」。無論巨集實際例項化的套件為何,在 //lib:defs.bzl 中定義的巨集直接建立的任何目標,都可從 //lib 中定義的任何其他巨集查看。同樣地,他們可以查看直接在 //lib/BUILD 和舊版巨集中宣告的目標,也可以讓這些目標看到。反之,如果至少有一個目標是由符號巨集建立,則同一個套件中的目標不一定能看到彼此。

在符號巨集的實作函式中,visibility 參數在附加巨集呼叫位置後,會具有巨集 visibility 屬性的有效值。巨集將其中一個目標匯出至呼叫端的標準方式,是將這個值轉送至目標的宣告,如 some_rule(..., visibility = visibility) 所示。如果目標省略這項屬性,除非呼叫端與巨集定義位於同一個套件中,否則呼叫端不會看到目標。這種行為會組成一連串巢狀呼叫,每個呼叫都會傳遞 visibility = visibility,並在每個層級將內部巨集的匯出目標重新匯出至呼叫端,不會公開任何巨集的實作詳細資料。

將權限委派給子巨集

顯示模型具有特殊功能,可讓巨集將權限委派給子巨集。這對巨集的因式分解和組合來說非常重要。

假設您有一個巨集 my_macro,可使用另一個套件的規則 some_library 建立依附元件邊緣:

# //macro/defs.bzl
load("//lib:defs.bzl", "some_library")

def _impl(name, visibility, ...):
    ...
    native.genrule(
        name = name + "_dependency"
        ...
    )
    some_library(
        name = name + "_consumer",
        deps = [name + "_dependency"],
        ...
    )

my_macro = macro(implementation = _impl, ...)
# //pkg/BUILD

load("//macro:defs.bzl", "my_macro")

my_macro(name = "foo", ...)

//pkg:foo_dependency 目標未指定 visibility,因此只會在 //macro 中顯示,這對取用目標來說沒有問題。現在,如果 //lib 的作者將 some_library 重構為使用巨集實作,會發生什麼情況?

# //lib:defs.bzl

def _impl(name, visibility, deps, ...):
    some_rule(
        # Main target, exported.
        name = name,
        visibility = visibility,
        deps = deps,
        ...)

some_library = macro(implementation = _impl, ...)

這項變更後,//pkg:foo_consumer 的位置現在是 //lib,而非 //macro,因此 //pkg:foo_consumer 使用 //pkg:foo_dependency 會違反依附元件的可見度。作者不應為瞭解決這個實作細節,而將 my_macro 傳遞至依附元件的宣告。visibility = ["//lib"]

因此,如果目標的依附元件也是宣告目標的巨集屬性值,我們會根據巨集的位置檢查依附元件的可見性,而不是根據取用目標的位置。

在這個範例中,如要驗證 //pkg:foo_consumer 是否能看到 //pkg:foo_dependency,我們會發現 //pkg:foo_dependency 也做為輸入內容傳遞至 my_macro 內的 some_library 呼叫,並改為根據這個呼叫的位置 //macro 檢查依附元件的可見度。

只要目標或巨集宣告位於另一個符號巨集內,並在其中一個標籤型別屬性中採用依附元件的標籤,這個程序就會以遞迴方式重複執行。

Finalizers

除了遵循一般符號巨集顯示規則的目標,規則終結器 (含 finalizer = True 的符號巨集) 中宣告的目標可查看終結器目標套件可見的所有目標。

換句話說,如果您將以 native.existing_rules() 為基礎的舊版巨集遷移至終結器,終結器宣告的目標仍可查看舊版依附元件。

您可以定義最終程式碼可使用 native.existing_rules() 內省的目標,但這些目標無法做為可見度系統下的依附元件。舉例來說,如果巨集定義的目標對自己的套件或終結器巨集的定義不可見,且未委派給終結器,終結器就無法看到這類目標。但請注意,以 native.existing_rules() 為基礎的舊版巨集也無法看到這類目標。

載入顯示設定

載入顯示狀態:控制是否可從目前套件以外的其他 BUILD.bzl 檔案載入 .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 檔案宣告可見度的情況下,工作區不需要這項檢查。