長寬比

回報問題 查看原始碼

本頁面說明使用切面的基本知識和優點,並提供簡易和進階的範例。

這些切面可讓您擴充建構依附元件圖表,內含額外資訊和動作。以下是一些可派上用場的典型情境:

  • 整合 Bazel 的 IDE 可以使用各種層面來收集專案的相關資訊。
  • 程式碼產生工具可以利用各層面,以跨目標的方式執行輸入內容。舉例來說,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 檔案),並將資訊 (例如這些構件的位置和名稱) 傳遞至供應商中的反向依附元件。

切面與規則類似,其具有可產生動作並傳回提供者的實作函式。但其功力來自於建構依附元件圖的方式。切麵包含實作項目,以及其所包含的所有屬性的清單。假設有一個面向 A,該元素會在名為「deps」的屬性一併傳播。這個切面可套用至目標 X,產生切面應用程式節點 A(X)。在應用程式執行期間,系統會遞迴將 A 套用至其「deps」屬性中參照的所有目標 (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 的另一個常見引數是 ['*'],這會將切面套用到規則的所有屬性。

切面實作

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"
        )
    }
...

切面實作

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 的提供者聯集,從導入目標 X 的規則和切面 A 的實作所衍生的提供者。系統會先建立並凍結規則實作傳播的提供者,然後再套用面向,該提供者無法從特定部分修改。如果目標和套用的切面提供相同類型的提供者,但 OutputGroupInfo (合併,只要規則和切面指定不同的輸出群組) 和 InstrumentedFilesInfo (從切面取得) 除外,就會發生錯誤。這表示切面實作可能不會傳回 DefaultInfo

參數和不公開屬性會透過 ctx 的屬性傳遞。這個範例會參照 extension 參數,並決定要計算哪些檔案。

如果是傳回提供者,則傳遞切面的屬性值 (來自 attr_aspects 清單) 會替換為向這些提供者顯示尺寸的應用程式結果。舉例來說,如果目標 X 的單位為 Y 和 Z,則 A(X) 的 ctx.rule.attr.deps 將會是 [A(Y, A(Z)])。在本範例中,ctx.rule.attr.deps 是目標物件,系統會將顯示切面套用至套用長寬比的原始目標「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 以遞迴方式存取所有目標。

參考資料