本頁面說明使用切面的基本概念和優點,並提供簡單的進階範例。
切面可擴增建構依附元件圖表,提供額外資訊和動作。切面適用的幾個常見情況:
- 整合 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 會針對上述範例中的每個目標呼叫對應的 rule (在這個範例中為「java_library」) 的實作函式,藉此分析這個依附元件圖。規則實作函式會產生建構構件的動作 (例如 .jar
檔案),然後將資訊 (例如這些構件的位置和名稱) 傳遞至提供者中這些目標的反向依附元件。
切面與規則類似,它們具有產生動作及傳回提供者的實作函式。不過,這些函式的威力來自於建構依附元件圖表的方式。一個面向具有實作項目,以及其隨移動的所有屬性清單。假設有一個面向 A,會沿著名為「deps」的屬性傳播。這個切面可套用至目標 X,產生切面應用程式節點 A(X)。在應用程式內,系統會遞迴套用 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
的另一個常見引數是 ['*']
,這個引數會將規格套用到規則的所有屬性。
切面實作
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 []
切面實作函式與規則實作函式類似。其會傳回「提供者」、產生動作,並採用兩個引數:
實作函式可透過 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
定義切面的一組屬性。公開切面屬性會定義參數,且只能為 bool
、int
或 string
類型。如果是規則傳播的部分,則必須針對 int
和 string
參數指定 values
。本範例有一個名為 extension
的參數,可將「*
」、「h
」或「cc
」設為值。
如果是規則傳播層面,參數值是從要求切面的規則取得,並使用具有相同名稱和類型的規則屬性。(請參閱 file_count_rule
的定義)。
對於指令列層面而言,您可以使用 --aspects_parameters
標記傳遞參數值。可能會省略 int
和 string
參數的 values
限制。
切面也可以具有 label
或 label_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 的依附元件包含 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
以遞迴方式存取所有目標。