Bazel 專案
Bazel 專案是來源和建構檔案的集合,用於說明下列項目:
- 如何建構二進位檔或資料檔案等構件,以及這些構件的依附元件。
- 如何執行特定指令,例如指令碼或可執行檔。
- 如何測試上述建構成果。
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,僅適用於 Linux 和大小寫敏感的檔案系統。Blaze 只使用檔案名稱 BUILD
來儲存建構指示。
不過,Bazel 也需要在 Windows 上執行,而 Windows 的檔案系統不區分大小寫,許多 Google 或非 Google 專案都已使用名為「build
」的目錄,因此會與此類系統上的「BUILD
」檔案衝突。
為解決這個問題,Bazel 會使用 BUILD.bazel
和 WORKSPACE.bazel
做為預設檔案名稱,同時仍支援 BUILD
和 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
標示,在外部存放區 (例如 @foo//:BUILD.bazel
內) 中則以 @acme_anvil
標示。所有三個標籤都會參照同一個目錄的內容。
標準存放區名稱不會顯示在 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