規則教學課程

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

Starlark 是一種類似 Python 的設定語言,最初是為 Bazel 開發,後來也用於其他工具。Bazel 的 BUILD.bzl 檔案是以 Starlark 方言編寫,正確來說是「建構語言」,但通常簡稱為「Starlark」,特別是強調某項功能是以建構語言表示,而非 Bazel 的內建或「原生」部分時。Bazel 會使用許多與建構相關的函式 (例如 globgenrulejava_binary 等),擴增核心語言。

詳情請參閱 BazelStarlark 說明文件,並以 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)

即使規則沒有任何作用,行為仍與其他規則相同:具有必要名稱,並支援 visibilitytestonlytags 等常見屬性。

評估模型

在進一步瞭解之前,請務必先瞭解程式碼的評估方式。

使用一些列印陳述式更新 foo.bzl

def _foo_binary_impl(ctx):
    print("analyzing", ctx.label)

foo_binary = rule(
    implementation = _foo_binary_impl,
)

print("bzl file evaluation")

和 BUILD:

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 evaluation」。評估 BUILD 檔案前,Bazel 會先評估載入的所有檔案。如果載入多個 BUILD 檔案 foo.bzl,您只會看到一次「bzl 檔案評估」,因為 Bazel 會快取評估結果。
  • 系統不會呼叫回呼函式 _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 檔案評估」和「BUILD 檔案」,因為在呼叫 bazel query 後,foo.bzl 的評估結果會快取。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]))]

稍後再查看 DefaultInfodepset 函式。目前,請假設最後一行是選擇規則輸出內容的方式。

現在執行 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 的說明文件。您也可以使用其他類型的屬性,例如布林值整數清單

依附元件

依附元件屬性 (例如 attr.labelattr.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"])

進階操作