Starlark 是類似 Python 的設定語言,最初開發在 Bazel 中使用,且已由其他工具採用。Bazel 的 BUILD
和 .bzl
檔案是以其正確稱為「Build Language」的方言編寫,不過通常簡稱為「Starlark」,在強調某個功能是以 Build Language 表示,而不是在 Bazel 的內建或「原生」部分中。Bazel 提供多項建構相關函式 (例如 glob
、genrule
、java_binary
等),藉此強化核心語言。
詳情請參閱 Bazel 和 Starlark 文件,並以 Rules SIG 範本做為新規則集的起點。
空白規則
如要建立第一項規則,請建立 foo.bzl
檔案:
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
呼叫 rule
函式時,您必須定義回呼函式。邏輯會移至該位置,但您現在可以將函式留空。ctx
引數提供目標的相關資訊。
您可以載入規則,並從 BUILD
檔案使用。
在同一個目錄中建立 BUILD
檔案:
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
現在,您可以建構目標:
$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)
雖然規則沒有任何作用,但運作方式類似其他規則:內含必要名稱,且支援 visibility
、testonly
和 tags
等常見屬性。
評估模型
在繼續之前,請務必瞭解程式碼的評估方式。
使用部分輸出陳述式更新 foo.bzl
:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
打造:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
對應於要分析的目標標籤。ctx
物件有許多實用欄位和方法;您可以參閱 API 參考資料查看完整清單。
查詢程式碼:
$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1
建議您留意以下幾點:
- 系統會先列印「bzl file valuation」。評估
BUILD
檔案之前,Bazel 會評估其載入的所有檔案。如果同時載入多個BUILD
檔案,由於 Bazel 會快取評估結果,因此您只會看到一次「bzl file valuation」(bzl 檔案評估) 事件。 - 系統不會呼叫回呼函式
_foo_binary_impl
。Bazel 查詢會載入BUILD
檔案,但不會分析目標。
如要分析目標,請使用 cquery
(「設定的查詢」) 或 build
指令:
$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
如您所見,_foo_binary_impl
現已呼叫兩次,每個目標各一次。
請注意,系統不會再次列印「bzl file valuation」和「BUILD file」,因為 foo.bzl
的評估會在呼叫 bazel query
後快取。Bazel 只會在實際執行時發出 print
陳述式。
正在建立檔案
如要讓規則更實用,請更新規則來產生檔案。首先,宣告檔案並為檔案命名。在此範例中,建立與目標同名的檔案:
ctx.actions.declare_file(ctx.label.name)
如果現在執行 bazel build :all
,您會收到錯誤訊息:
The following files have no generating action:
bin2
宣告檔案時,您必須建立動作,讓 Bazel 知道如何產生檔案。使用 ctx.actions.write
建立含有指定內容的檔案。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
程式碼有效,但不會執行任何動作:
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
ctx.actions.write
函式會登錄動作,藉此教導 Bazel 如何產生檔案。不過,除非實際要求,Bazel 才會建立檔案。最後一個要做的方法是告知 Bazel,該檔案是規則的輸出,而不是在規則實作中使用的暫存檔案。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello!\n",
)
return [DefaultInfo(files = depset([out]))]
請稍後查看 DefaultInfo
和 depset
函式。目前,假設最後一行是選擇規則輸出的方式。
現在,請執行 Bazel:
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
您已成功產生檔案!
屬性
如要讓規則更實用,請使用 attr
模組新增屬性並更新規則定義。
新增名為 username
的字串屬性:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
接著,請在 BUILD
檔案中設定:
foo_binary(
name = "bin",
username = "Alice",
)
如要存取回呼函式中的值,請使用 ctx.attr.username
。例如:
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello {}!\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
請注意,您可以將這項屬性設為必要,也可以設定預設值。查看 attr.string
的說明文件。您也可以使用其他類型的屬性,例如boolean或整數清單。
依附元件
依附元件屬性 (例如 attr.label
和 attr.label_list
) 會宣告擁有該屬性的目標的依附元件,並宣告其標籤顯示在屬性值的目標目標中。這種屬性是目標圖表的基礎。
在 BUILD
檔案中,目標標籤會顯示為字串物件,例如 //pkg:name
。在實作函式中,目標會以 Target
物件的形式存取。例如,您可以使用 Target.files
查看目標傳回的檔案。
多個檔案
根據預設,只有由規則建立的目標可能會顯示為依附元件 (例如 foo_library()
目標)。如果您想讓屬性接受輸入檔案的目標 (例如存放區中的來源檔案),您可以使用 allow_files
,並指定可接受的副檔名清單 (或是 True
允許任何副檔名):
"srcs": attr.label_list(allow_files = [".java"]),
您可以透過 ctx.files.<attribute name>
存取檔案清單。例如,srcs
屬性中的檔案清單可透過
ctx.files.srcs
單一檔案
如果只需要一個檔案,請使用 allow_single_file
:
"src": attr.label(allow_single_file = [".java"])
這個檔案隨後即可透過 ctx.file.<attribute name>
存取:
ctx.file.src
使用範本建立檔案
您可以建立規則,根據範本產生 .cc 檔案。此外,您可以使用 ctx.actions.write
輸出在規則實作函式中建構的字串,但這有兩個問題。首先,隨著範本規模變大,放在個別檔案中,並且避免在分析階段建構大型字串,記憶體的效率會更高。第二,使用不同的檔案對使用者來說會比較方便。請改用 ctx.actions.expand_template
,這會在範本檔案上執行替換。
建立 template
屬性以宣告範本檔案的依附元件:
def _hello_world_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".cc")
ctx.actions.expand_template(
output = out,
template = ctx.file.template,
substitutions = {"{NAME}": ctx.attr.username},
)
return [DefaultInfo(files = depset([out]))]
hello_world = rule(
implementation = _hello_world_impl,
attrs = {
"username": attr.string(default = "unknown person"),
"template": attr.label(
allow_single_file = [".cc.tpl"],
mandatory = True,
),
},
)
使用者可以使用以下規則設定:
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
如果您不想向使用者顯示範本,而且一律使用相同的範本,您可以設定預設值,並將屬性設為不公開:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
以底線開頭的屬性屬於不公開屬性,不能在 BUILD
檔案中設定。範本現在是隱含依附元件:每個 hello_world
目標都有對這個檔案的依附元件。別忘了更新 BUILD
檔案並使用 exports_files
,讓其他套件能夠檢視此檔案:
exports_files(["file.cc.tpl"])