長寬比

回報問題 查看來源

本頁面說明使用外觀的基本與優點,並提供簡易和進階範例。

這些切面可讓您利用額外資訊和動作來擴充建構依附元件圖表。以下是一些能派上用場的常見情境:

  • 整合 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 會以遞迴方式套用至 X 參照「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 的規則實作,以及從 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 是目標物件,這類物件是將長寬比套用至原始目標的「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 遞迴評估所有目標。

參考資料