規則教學課程

回報問題 查看來源 夜間 7.2 7.1 7.0 6.5 6.4

Starlark 是類似於 Python 的程式 最初開發需要在 Bazel 中使用的設定語言,並採用 也幫助了使用者Bazel 的 BUILD.bzl 檔案是以 Starlark 一般稱為「Build Language」(建構語言) 簡稱為「Starlark」,尤其是在強調某項功能 而不是內建或「原生」架構零件 可在 Bazel 開始進行Bazel 會透過多個建構相關函式來增強核心語言 例如 globgenrulejava_binary

詳情請參閱 BazelStarlark 說明文件: 更多詳細資料 規則 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")

並構思:

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 檔案評估」評估 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"])

再升級