本頁面將介紹 Bazel 的兩個可見度系統:目標可見度和載入可見度。
這兩種可見度設定都能協助其他開發人員區分您的程式庫公開 API 與其實作詳細資料,並在您的工作區擴大時協助強制執行結構。您也可以在淘汰公開 API 時使用可見性,允許現有使用者使用,但拒絕新使用者。
指定可視度
目標對象瀏覽權限可控管哪些使用者可能會依附目標對象,也就是哪些使用者可能會在 deps
等屬性中使用目標對象的標籤。如果目標違反其中一個依附元件的可見度,就會在分析階段中建構失敗。
一般來說,如果目標 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:mytarget
將 visibility
設為 [":__subpackages__", "//tests:__pkg__"]
,則 //some/package:mytarget
可供 //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
檔案上呼叫 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
可為這些目標啟用可見度檢查功能。為了協助遷移作業,它也會將未指定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
目標檢查該依附元件。
如果您想將規則的使用權限限制在特定套件,請改用載入可見度。
可見度和符號巨集
本節說明可視度系統與符號巨集的互動方式。
符號巨集中的地點
可見度系統的關鍵細節是如何判斷宣告的位置。如果目標並未在符號巨集中宣告,位置就是目標所在的套件,也就是 BUILD
檔案的套件。不過,如果是透過符號巨集建立的目標,位置則是包含 .bzl
檔案的套件,其中會顯示巨集定義 (my_macro = macro(...)
陳述式)。在多個巢狀目標中建立目標時,系統一律會使用最內層符號巨集的定義。
系統會使用相同的系統,判斷要根據哪個依附元件的可見度,檢查哪個位置。如果使用目標是在巨集中建立,我們會查看最內層巨集的定義,而非使用目標所在的套件。
也就是說,所有巨集的程式碼都會在同一個套件中定義,並自動成為彼此的「朋友」。無論巨集實際在哪個套件中例項,只要是 //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_dependency
的使用方式違反了依附元件的可見度。my_macro
的作者無法只為瞭解決這個實作細節,而將 visibility = ["//lib"]
傳遞至依附元件的宣告。
因此,如果目標的依附元件也是宣告目標的巨集屬性值,我們會根據巨集的位置,而非使用目標的位置,檢查依附元件的可見度。
在這個範例中,為了驗證 //pkg:foo_consumer
是否能看到 //pkg:foo_dependency
,我們發現 //pkg:foo_dependency
也已傳遞為 my_macro
內 some_library
呼叫的輸入內容,因此改為檢查依附元件的可見度,對應至此呼叫的 //macro
位置。
只要目標或巨集宣告位於其他符號巨集中,並在其中一個標籤類型屬性中使用依附元件的標籤,這個程序就能重複遞迴。
載入瀏覽權限
載入可見度可控制是否可從目前套件以外的其他 BUILD
或 .bzl
檔案載入 .bzl
檔案。
就像目標瀏覽權限可保護由目標封裝的原始碼一樣,載入瀏覽權限可保護由 .bzl
檔案封裝的建構邏輯。舉例來說,BUILD
檔案作者可能會將一些重複的目標宣告納入 .bzl
檔案中的巨集。如果沒有載入可見度的保護措施,他們可能會發現同一個工作區中的其他協作者重複使用巨集,因此修改巨集會導致其他團隊的建構作業中斷。
請注意,.bzl
檔案可能有或沒有對應的來源檔案目標。如果是這樣,則無法保證載入可見度和目標可見度會一致。也就是說,同一個 BUILD
檔案可能可以載入 .bzl
檔案,但不會在 filegroup
的 srcs
中列出該檔案,反之亦然。這可能會導致規則無法使用 .bzl
檔案做為原始碼,例如產生說明文件或進行測試。
如要製作原型,您可以設定 --check_bzl_visibility=false
來停用載入顯示權限。與 --check_visibility=false
一樣,這項操作不應針對提交的程式碼執行。
載入可見度功能自 Bazel 6.0 版起推出。
宣告載入可見度
如要設定 .bzl
檔案的載入可見度,請在檔案中呼叫 visibility()
函式。visibility()
的引數是套件規範清單,就像 package_group
的 packages
屬性一樣。不過,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
當使用者從名為 internal
或 private
的目錄載入檔案時,如果使用者的檔案本身並未位於該目錄的父項下方,就會出現 Buildifier lint 提供的警告。這項 Lint 會在載入可見度功能之前執行,因此在 .bzl
檔案宣告可見度的工作區中,這項 Lint 就沒有必要。