Bazel 專案
Bazel 專案是一組來源和建構檔案,用於描述:
- 如何建構二進位檔或資料檔案等構件,以及相關依附元件。
- 如何run特定指令,例如指令碼或執行檔。
- 如何測試上述建構構件。
Bazel 專案會由頂層目錄具體化,此目錄的內容遵循特定的版面配置和慣例。
Bazel 工作區
Bazel 工作區是包含頂層 WORKSPACE.bazel
1 檔案的目錄樹狀結構。這會定義一組來源和相關建構檔案的根目錄。Bazel 專案可使用數個工作區:
專案的根目錄稱為根 工作區,因此必須包含
WORKSPACE.bazel
檔案。專案也可以參照「外部工作區」,這些工作區會對應到第三方專案依附元件。
範例如下:
/home/user/project/
WORKSPACE.bazel
src/
BUILD.bazel
extra/
extra.cc
lib/
BUILD.bazel
foo.cc
foo.h
main.cc
/home/user/project
目錄是根 Bazel 工作區,內有其中所有檔案。
WORKSPACE.bazel
檔案可以空白,但也可以包含參照其他工作區的指令,如後文所述。
Bazel 套件
在工作區中,含有 BUILD.bazel
2 檔案的目錄會定義「套件」,這是 Bazel 已知來源檔案和項目集合的邊界。
例如,下列檔案版面配置:
/home/user/project/
WORKSPACE.bazel
BUILD.bazel
main.cc
定義位於 /home/user/project
的根工作區,具有單一頂層套件,其中包含 BUILD.bazel
和 main.cc
檔案。
BUILD.bazel
檔案也可以包含用於定義具名項目的指令,例如目標、設定條件等,嚴格來說同樣屬於套件。
單一工作區中可有多個套件,且每個檔案只能屬於一個套件。以下列檔案版面配置為例:
/home/user/project/
WORKSPACE.bazel
BUILD.bazel
main.cc
lib/
BUILD.bazel
foo.cc
根工作區包含兩個不同的套件:
頂層套件,仍包含檔案
BUILD.bazel
和main.cc
(相對於根目錄目錄)。這是第二個套件,包含
lib/BUILD.bazel
和lib/foo.cc
檔案。
請注意,/home/user/project/lib/foo.cc
的檔案只屬於第二個套件,不屬於第一個套件。這是因為套件 邊界「永不」重疊。
BUILD.bazel
對BUILD
Bazel 出自 Google 的 Blaze,該 Blaze 只能在 Linux 上區分大小寫的檔案系統中運作。Blaze 只使用 BUILD
檔案名稱儲存建構指令。
不過,Bazel 也必須在具有不區分大小寫檔案系統的 Windows 和 MacOS 上執行,而且許多 Google 專案或非 Google 專案都已使用名為「build
」的目錄,然後在這類系統上與名為「BUILD
」的檔案衝突。
為解決這個問題,Bazel 使用 BUILD.bazel
和 WORKSPACE.bazel
做為預設檔案名稱,同時支援 BUILD
和 WORKSPACE
做為備用檔案名稱。
Workspace 指令
根 WORKSPACE.bazel
可以是空白的,或者包含參照其他 Bazel 工作區的指令 (稱為「外部存放區」)。這些指令一律會為存放區命名。例如:
local_repository(
name = "my_ssl",
path = "/home/user/src/openssl-bazel",
)
將名稱 my_ssl
與位於建構機器上 /home/user/src/openssl-bazel
的工作區建立關聯。這個目錄也必須包含 WORKSPACE.bazel
(或 WORKSPACE
) 檔案。
存放區只是具有名稱的外部 Bazel 工作區。這個名稱是專案本機的名稱,之後可用於參照外部工作區的項目 (請見下文)。
Bazel 也支援其他指令,可從網路下載存放區,或甚至以程式輔助方式產生存放區的內容。
Bazel 標籤
Bazel 標籤是 BUILD.bazel
檔案中定義的來源檔案和項目的字串參照。一般格式為:
@<repository_name>//<package_name>:<target_name>
在此情況下:
@<repository_name>//
會指定已命名 Bazel 工作區的目錄。為了方便起見,這可以簡單縮寫為目前工作區 (包含目前
BUILD.bazel
檔案的工作區) 的//
來縮寫。另請注意,@//
用於指定專案的根工作區,即使在外部存放區中使用亦然。<package_name>
是套件的目錄路徑 (相對於工作區目錄)。例如,在//src:main.cc
或//src/lib:foo
標籤中,套件名稱分別為src
和src/lib
。這可以空白,例如
//:BUILD.bazel
會指向目前工作區頂層目錄中的建構檔案。就來源檔案而言,
<target_name>
是與其父項套件目錄相對的檔案路徑,可能包含子目錄部分。例如//src:main.cc
或//src:extra/extra.cc
的目標名稱為main.cc
和extra/extra.cc
。如果是其他項目,
<target_name>
會對應BUILD.bazel
檔案中定義的項目 (建構構件、建構設定、設定條件等)。依照慣例,其
name
屬性不應包含目錄分隔符,不過在極少數情況下,為了避免與來源產生混淆。如果開發人員來自其他建構系統,而這些系統區分了建構圖中的項目類型 (例如
GN
使用「目標」、「設定」、「工具鍊」和「集區」來指定不同事物),這可能會讓使用者感到困惑。
系統也支援標籤的縮短運算式:
如果標籤以存放區名稱開頭且不含冒號,則代表套件路徑,並指向具有相同名稱的項目。舉例來說,
//src/foo
相當於//src/foo:foo
。如果標籤以冒號開頭,則為相對於目前套件的名稱。舉例來說,在
src/foo/BUILD.bazel
中顯示的「:bar
」和「:extra/bar.cc
」等於//src/foo:bar
和//src/foo:extra/bar.cc
。如果標籤沒有存放區名稱且沒有半形冒號,則該標籤一律會是與目前套件相關的名稱,即使其中包含目錄分隔符也一樣。例如,
src/foo/BUILD.bazel
中的「bar/bar.cc
」一律是指//src/foo:bar/bar.cc
。請注意,這與
//src/foo/bar:bar.cc
不同
相對標籤和套件擁有權
由於每個來源檔案只能屬於單一套件,因此相對標籤可能無效。例如,在如下所示的專案中:
/home/user/project/
WORKSPACE.bazel
src/
BUILD.bazel
main.cc
extra/
extra.cc
lib/
BUILD.bazel
foo.cc
foo.h
foo.cc
檔案屬於 src/lib
套件,因此其標籤必須為
//src/lib:foo.cc
。
在 src/BUILD.bazel
中使用 src:lib/foo.cc
之類的標籤會導致錯誤:
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"lib/foo.cc", # Error: Label '//src:lib/foo.cc' is invalid because 'src/lib' is a subpackage
"lib/foo.h" # Error: Label '//src:lib/foo.h' is invalid because 'src/lib' is a subpackage
"main.cc",
],
)
其他套件的來源檔案存取權
根據預設,指定套件的來源檔案無法從其他套件存取,而且相對套件標籤無效,如下所示:
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"lib:foo.cc", # Error: invalid label 'lib:foo.cc': absolute label must begin with '@' or '//'
"lib:foo.h" # Error: invalid label 'lib:foo.h': absolute label must begin with '@' or '//'
"main.cc",
],
)
即使使用正確的絕對標籤,仍會發生以下錯誤:
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"//src/lib:foo.cc", # Error: no such target '//src/lib:foo.cc': target 'foo.h' not declared in package 'src/lib'
"//src/lib:foo.h" # Error: no such target '//src/lib:foo.h': target 'foo.h' not declared in package 'src/lib'
"main.cc",
],
)
export_files()
可授予跨套件邊界的檔案直接存取權:
# From src/lib/BUILD.bazel
export_files([
"foo.cc" ,
"foo.h" ,
])
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"//src/lib:foo.cc", # OK
"//src/lib:foo.h" # OK
"main.cc",
],
)
指定來自其他套件的存取權
BUILD.bazel
檔案中定義的已加標籤項目不需要匯出,但其 visibility
屬性必須允許在其套件外使用這些項目:
# From src/lib/BUILD.bazel
cc_library(
name = " lib" ,
srcs = [ " foo.cc" ],
hdrs = [ " foo.h" ],
visibility = [ " //visibility:public" ], # Anyone can reference this directly!
)
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"main.cc",
],
deps = [ "lib" ], # OK!
)
根據預設,只有同一套件中的其他商品可以查看項目。您可以使用 package()
指令變更套件中定義的所有項目的預設瀏覽權限,藉此變更這項設定:
# From src/lib/BUILD.bazel
# Ensure that all items defined in this file are visible to anyone
package(default_visibility = ["//visibility:public"])
cc_library(
name = " lib" ,
srcs = [ "foo.cc" ],
hdrs = [ "foo.h" ],
)
# From src/BUILD.bazel
cc_binary(
name = "program",
srcs = [
"extra/extra.cc",
"main.cc",
],
deps = [ "lib" ], # OK!
)
虛擬套件的警告
請避免在專案中使用以下名稱建立頂層目錄:
conditions
command_line_option
external
visibility
因為 Bazel 在 BUILD.bazel
檔案的標籤中使用許多硬式編碼的「虛擬套件」。例如:
//visibility:public
//conditions:default
//command_line_option:copt
external
的情況略有不同:它不會顯示在 BUILD.bazel
檔案中,但會在內部用於管理外部存放區。將 Bazel 用做專案目錄時,這就會混淆
標準存放區名稱
自 Bazel 6.0 開始,標籤中的存放區名稱也可以以 @@
開頭。
啟用選用的 BzlMod 功能時,這些標籤會做為外部存放區的替代名稱不重複標籤名稱,在專案中使用複雜的遞移依附元件樹狀結構時,這個標籤會十分重要。
例如,@@com_acme_anvil.1.0.3
可以是在專案本身的 BUILD.bazel
檔案中由 @anvil
識別的工作區目錄標準名稱,也可以是 @acme_anvil
出現在外部存放區 (例如在 @foo//:BUILD.bazel
中) 時。這三個標籤都參照相同目錄的內容。
標準存放區名稱不會顯示在 BUILD.bazel
檔案中,但會在分析階段 (執行會查看標籤值的 Starlark 函式時) 或查看 Bazel 查詢的結果時顯示。
Bazel 副檔名 (.bzl
) 檔案
擴充功能檔案包含可匯入其他多個檔案的額外定義:
而且其名稱結尾一律為
.bzl
副檔名。這些記錄檔必須屬於 Bazel 套件,因此可以透過標籤識別。例如
//bazel_utils:defs.bzl
。這些程式庫是以 Starlark 語言編寫,因此應遵循特定規範。
是唯一可以定義 Starlark 函式的位置!換句話說,使用者無法在
BUILD.bazel
檔案中定義函式!即使匯入多次,系統一律會「評估一次」,且其定義的變數和函式會記錄為常數。
您可以使用
load()
陳述式,從其他檔案匯入這些函式。
例如:
來自
$PROJECT/my_definitions.bzl
:# The official release number release_version = "1.0.0"
來自
$PROJECT/BUILD.bazel
:# Import the value of `release_version` from my_definitions.bzl load("//:my_definitions.bzl", "release_version") # Compile C++ executable, hard-coding its version number with a macro. cc_binary( name = "my_program", defines = [ "RELEASE_VERSION=" + release_version ], sources = [ … ], )
load()
陳述式具有特殊的語意:
第一個引數必須是
.bzl
檔案的標籤字串 (例如"//src:definitions.bzl"
)。其他引數名稱匯入的常數或函式:
如果引數是字串,值必須是
.bzl
檔案定義的匯入符號名稱。例如:load("//src:defs.bzl", "my_var", "my_func")
如果引數是變數指派,會定義匯入符號的本機別名。E.g.:
load("//src:defs.bzl", "my_var", func = "my_func")
沒有萬用字元:所有匯入的常數與函式都必須明確命名。
當
load()
出現在.bzl
檔案中時,系統一律不會記錄匯入的符號。同樣地,系統也不會記錄名稱開頭為與底線 (例如
_foo
) 的符號,且無法匯入。亦即這些都是私人項目,僅供定義這些屬性的.bzl
檔案使用。
.bzl
檔案有時想從另一個檔案匯入一個符號,並以相同名稱重新匯出。按照以下方式要求別名:
# From //src:utils.bzl
# Import "my_vars" from defs.bzl as '_my_var'.
load("//src:defs.bzl", _my_var = "my_var")
# Define my_var in the current scope as a copy of _my_var
# This symbol and its value will be recorded, and available for import
# to any other file that loads //src:utils.bzl.
my_var = _my_var