長寬比

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

本頁面說明使用層面的基本概念和好處,並提供簡單和進階的範例。

層面可讓您使用額外資訊和動作擴增建構依附元件圖表。以下列舉一些常見情況,說明層面可能很有用:

  • 整合 Bazel 的 IDE 可以使用 Aspect 收集專案相關資訊。
  • 程式碼產生工具可以利用各方面,以不限目標的方式執行輸入內容。舉例來說,BUILD 檔案可以指定 protobuf 程式庫定義的階層,而語言專屬規則可以使用構面,附加為特定語言產生 protobuf 支援程式碼的動作。

切面基本概念

BUILD 檔案會說明專案的原始碼:哪些來源檔案屬於專案、應從這些檔案建構哪些構件 (目標),以及這些檔案之間的相依性等。Bazel 會使用這項資訊執行建構作業,也就是找出產生構件所需的一組動作 (例如執行編譯器或連結器),並執行這些動作。Bazel 會建構目標之間的依附元件圖表,並瀏覽這個圖表來收集這些動作。

請參考下列 BUILD 檔案:

java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)

這個 BUILD 檔案定義的依附元件關係圖如下圖所示:

建構圖表

圖 1. BUILD 檔案依附元件圖表。

Bazel 會針對上述範例中的每個目標,呼叫對應規則 (在本例中為「java_library」) 的實作函式,藉此分析這個依附元件圖。規則實作函式會產生建構構件的動作,例如 .jar 檔案,並將這些構件的位置和名稱等資訊,傳遞至 providers 中這些目標的反向依附元件。

與規則類似,構面也有實作函式,可產生動作並傳回供應器。不過,這些功能的力量來自於為其建構依附元件圖表的方式。每個層面都有實作項目,以及傳播的所有屬性清單。假設屬性 A 沿著名為「deps」的屬性傳播。這個面向可以套用至目標 X,產生面向應用程式節點 A(X)。在套用期間,系統會將面向 A 遞迴套用至 X 在「deps」屬性中參照的所有目標 (A 傳播清單中的所有屬性)。

因此,將構面 A 套用至目標 X 的單一動作,會產生目標原始依附元件圖的「陰影圖」,如下圖所示:

使用切面建構圖表

圖 2. 使用各個層面建構圖表。

只有傳播集中的屬性邊緣會加上陰影,因此在本例中,runtime_deps 邊緣不會加上陰影。接著,系統會在陰影圖中的所有節點上叫用層面實作函式,類似於在原始圖的節點上叫用規則實作函式。

簡單範例

這個範例說明如何以遞迴方式,列印規則及其所有具有 deps 屬性的依附元件的來源檔案。這個範例會顯示層面實作、層面定義,以及如何從 Bazel 指令列叫用層面。

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

我們將範例分成幾個部分,並逐一檢查。

切面定義

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

層面定義與規則定義類似,且使用 aspect 函式定義。

與規則相同,構面也有實作函式,在這個範例中為 _print_aspect_impl

attr_aspects 是規則屬性清單,方面會沿著這些屬性傳播。 在這種情況下,該方面會沿著套用規則的 deps 屬性傳播。

attr_aspects 的另一個常見引數是 ['*'],這會將層面傳播至規則的所有屬性。

實作 Aspect

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

層面實作函式與規則實作函式類似。這些函式會傳回「供應商」,可以產生「動作」,並採用兩個引數:

  • target:要套用層面的目標
  • ctxctx 物件,可用於存取屬性,以及產生輸出內容和動作。

實作函式可以透過 ctx.rule.attr 存取目標規則的屬性。它可以檢查目標提供的供應商 (透過 target 引數)。

如要傳回供應商清單,必須提供相關面向。在本例中,這個層面不會提供任何內容,因此會傳回空白清單。

使用指令列叫用構面

如要套用層面,最簡單的方法是使用 --aspects 引數,透過指令列套用。假設上述方面是在名為 print.bzl 的檔案中定義:

bazel build //MyExample:example --aspects print.bzl%print_aspect

會將 print_aspect 套用至目標 example,以及透過 deps 屬性以遞迴方式存取的所有目標規則。

--aspects 旗標會採用一個引數,也就是 <extension file label>%<aspect top-level name> 格式的層面規格。

進階範例

以下範例說明如何使用目標規則中的層面,計算目標中的檔案數,並依副檔名篩選檔案。本文說明如何使用供應器傳回值、如何使用參數將引數傳遞至構面實作項目,以及如何從規則叫用構面。

file_count.bzl 檔案:

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

BUILD.bazel 檔案:

load('//:file_count.bzl', 'file_count_rule')

cc_library(
    name = 'lib',
    srcs = [
        'lib.h',
        'lib.cc',
    ],
)

cc_binary(
    name = 'app',
    srcs = [
        'app.h',
        'app.cc',
        'main.cc',
    ],
    deps = ['lib'],
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

切面定義

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

這個範例說明如何透過 deps 屬性傳播層面。

attrs 會定義某個層面的屬性集。公開層面屬性會定義參數,且只能是 boolintstring 類型。如果是規則傳播的層面,intstring 參數必須指定 values。這個範例有一個名為 extension 的參數,允許的值為「*」、「h」或「cc」。

如果是規則傳播的層面,系統會使用名稱和類型相同的規則屬性,從要求層面的規則中取得參數值。(請參閱 file_count_rule 的定義)。

就指令列層面而言,參數值可使用 --aspects_parameters 旗標傳遞。intstring 參數的 values 限制可能遭到省略。

此外,也允許方面具有 labellabel_list 類型的私有屬性。私有標籤屬性可用於指定對工具或程式庫的依附元件,這些工具或程式庫是方面所產生動作的必要條件。這個範例中未定義私有屬性,但下列程式碼片段示範如何將工具傳遞至層面:

...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "exec"
        )
    }
...

實作 Aspect

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

與規則實作函式一樣,構面實作函式會傳回可供依附元件存取的供應器結構體。

在本例中,FileCountInfo 定義為具有一個 count 欄位的供應器。最佳做法是使用 fields 屬性,明確定義供應商的欄位。

面向應用程式 A(X) 的供應商集,是來自目標 X 規則實作和面向 A 實作的供應商聯集。規則實作傳播的供應商會在套用構面之前建立並凍結,且無法從構面修改。如果目標和套用至目標的構面各自提供相同類型的供應商,就會發生錯誤,但 OutputGroupInfo (只要規則和構面指定不同的輸出群組,就會合併) 和 InstrumentedFilesInfo (取自構面) 除外。這表示方面實作可能永遠不會傳回 DefaultInfo

參數和私人屬性會傳遞至 ctx 的屬性中。這個範例會參照 extension 參數,並判斷要計算哪些檔案。

如果是回傳供應商,系統會將屬性的值 (即從 attr_aspects 清單傳播的層面) 替換為套用層面後的結果。舉例來說,如果目標 X 的 deps 中有 Y 和 Z,則 A(X) 的 ctx.rule.attr.deps 會是 [A(Y), A(Z)]。在本例中,ctx.rule.attr.deps 是 Target 物件,將層面套用至原始目標的「deps」後,就會產生這些物件。

在這個範例中,構面會從目標的依附元件存取 FileCountInfo 提供者,以累計檔案的總遞移數量。

從規則叫用構面

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

規則實作範例會說明如何透過 ctx.attr.deps 存取 FileCountInfo

規則定義會說明如何定義參數 (extension) 並為其指定預設值 (*)。請注意,如果預設值不是「cc」、「h」或「*」,則會因層面定義中對參數的限制而發生錯誤。

透過目標規則叫用構面

load('//:file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

這項操作示範如何透過規則將 extension 參數傳遞至構面。由於 extension 參數在規則實作中具有預設值,因此 extension 會視為選用參數。

建構 file_count 目標時,系統會評估我們的層面本身,以及透過 deps 遞迴存取的所有目標。

參考資料