如果目標 A
在建構或執行階段需要目標 B
,則目標 A
「依附於」目標 B
。「depends upon」關係會在目標上產生有向非循環圖 (DAG),稱為「依附元件圖」。
目標的直接依附元件是指依附元件圖中路徑長度為 1 的其他目標。目標的遞移依附元件是指目標透過圖表中的任意長度路徑依附的目標。
事實上,在建構作業的脈絡中,有兩個依附元件圖表,分別是「實際依附元件」圖表和「已宣告依附元件」圖表。在大多數情況下,這兩張圖表非常相似,因此不需要做出區別,但這對下方的討論很有幫助。
實際和已宣告的依附元件
如果 Y
必須存在、建構及保持最新狀態,X
才能正確建構,則目標 X
「實際上取決於目標 Y
。「建構」可能代表生成、處理、編譯、連結、封存、壓縮、執行,或建構期間經常發生的任何其他類型工作。
如果 X
的套件中存在從 X
到 Y
的依附元件邊緣,則目標 X
會對目標 Y
宣告依附元件。
如要正確建構,實際依附元件圖表 A 必須是已宣告依附元件圖表 D 的子圖表。也就是說,A 中每對直接連線的節點 x --> y
,在 D 中也必須直接連線。可以說 D 是 A 的過度近似。
BUILD
檔案編寫器必須向建構系統明確宣告每個規則的所有實際直接依附元件,且不得多報。
如果未遵守這項原則,就會導致未定義的行為:建構作業可能會失敗,但更糟的是,建構作業可能會依附於某些先前的作業,或是目標剛好具有的遞移宣告依附元件。Bazel 會檢查缺少的依附元件並回報錯誤,但這種檢查不一定能在所有情況下完成。
您不必 (也不應) 嘗試列出所有間接匯入的項目,即使這些項目在執行階段需要 A
也是如此。
建構目標 X
時,建構工具會檢查 X
的整個遞移閉包依附元件,確保這些目標的任何變更都會反映在最終結果中,並視需要重建中介項目。
依附元件的遞移性質會導致常見錯誤。有時,一個檔案中的程式碼可能會使用間接依附元件提供的程式碼,也就是宣告依附元件圖表中的遞移邊緣,但不是直接邊緣。間接依附元件不會顯示在 BUILD
檔案中。由於規則不會直接依附於供應商,因此無法追蹤變更,如下列時間軸範例所示:
1. 宣告的依附元件與實際依附元件相符
一開始一切正常,套件 a
中的程式碼會使用套件 b
中的程式碼。
套件 b
中的程式碼會使用套件 c
中的程式碼,因此 a
遞移地依附於 c
。
a/BUILD |
b/BUILD |
---|---|
rule( name = "a", srcs = "a.in", deps = "//b:b", ) |
rule( name = "b", srcs = "b.in", deps = "//c:c", ) |
a / a.in |
b / b.in |
import b; b.foo(); |
import c; function foo() { c.bar(); } |
|
|
宣告的依附元件會過度估算實際的依附元件。一切安好。
2. 新增未宣告的依附元件
如果有人在 a
中加入程式碼,直接實際依附於 c
,但忘記在建構檔案 a/BUILD
中宣告,就會產生潛在危害。
a / a.in |
|
---|---|
import b; import c; b.foo(); c.garply(); |
|
|
|
宣告的依附元件不再過度近似實際依附元件。這可能會建構成功,因為兩個圖的遞移閉包相等,但會遮蓋問題:a
實際依附於 c
,但未宣告。
3. 聲明與實際依附元件圖表之間的差異
當有人重構 b
,使其不再依附於 c
時,就會顯現危險,無意間破壞 a
,但這並非他們的錯。
b/BUILD |
|
---|---|
rule( name = "b", srcs = "b.in", deps = "//d:d", ) |
|
b / b.in |
|
import d; function foo() { d.baz(); } |
|
|
|
即使是遞移封閉,宣告的依附元件圖表現在仍是實際依附元件的低估值,因此建構作業可能會失敗。
只要確保步驟 2 中從 a
到 c
到 c
導入的實際依附元件已在 BUILD
檔案中正確宣告,就能避免這個問題。
依附元件類型
大多數建構規則都有三個屬性,可指定不同種類的泛型依附元件:srcs
、deps
和 data
。詳情請參閱下文。詳情請參閱所有規則通用的屬性。
許多規則也有其他屬性,可供規則專屬的依附元件類型使用,例如 compiler
或 resources
。詳情請參閱建構百科全書。
srcs
依附元件
規則直接使用的檔案,或輸出來源檔案的規則。
deps
依附元件
規則指向提供標頭檔案、符號、程式庫、資料等的個別編譯模組。
data
依附元件
建構目標可能需要一些資料檔案才能正確執行。這些資料檔案並非原始碼,因此不會影響目標的建構方式。舉例來說,單元測試可能會比較函式的輸出內容與檔案內容。建構單元測試時不需要這個檔案,但執行測試時需要。執行期間啟動的工具也適用相同規定。
建構系統會在獨立目錄中執行測試,只有列為 data
的檔案可供使用。因此,如果二進位檔/程式庫/測試需要某些檔案才能執行,請在 data
中指定這些檔案 (或包含這些檔案的建構規則)。例如:
# I need a config file from a directory named env:
java_binary(
name = "setenv",
...
data = [":env/default_env.txt"],
)
# I need test data from another directory
sh_test(
name = "regtest",
srcs = ["regtest.sh"],
data = [
"//data:file1.txt",
"//data:file2.txt",
...
],
)
這些檔案可透過相對路徑 path/to/data/file
存取。在測試中,您可以將測試的來源目錄路徑和工作區相對路徑合併,藉此參照這些檔案,例如 ${TEST_SRCDIR}/workspace/path/to/data/file
。
使用標籤參照目錄
查看 BUILD
檔案時,您可能會發現部分 data
標籤是指目錄。這些標籤會以 /.
或 /
結尾,例如:
請勿使用這些標籤:
不建議使用 -
data = ["//data/regression:unittest/."]
不建議使用 -
data = ["testdata/."]
不建議使用 -
data = ["testdata/"]
這項功能似乎很方便,特別是對於測試而言,因為測試可以運用目錄中的所有資料檔案。
但請盡量避免這麼做。為確保變更後能正確進行漸進式重建 (以及重新執行測試),建構系統必須瞭解建構 (或測試) 的完整輸入檔案集。指定目錄時,只有在目錄本身變更 (新增或刪除檔案) 時,建構系統才會執行重建作業,但無法偵測個別檔案的編輯作業,因為這些變更不會影響封閉目錄。您應列舉目錄中包含的檔案集 (明確列舉或使用 glob()
函式),而非將目錄指定為建構系統的輸入內容。(使用 **
強制 glob()
遞迴)。
建議 -
data = glob(["testdata/**"])
很抱歉,在某些情況下,您必須使用目錄標籤。舉例來說,如果 testdata
目錄包含名稱不符合標籤語法的檔案,明確列舉檔案或使用 glob()
函式就會產生無效標籤錯誤。在這種情況下,您必須使用目錄標籤,但請注意上述重建不正確的相關風險。
如果必須使用目錄標籤,請注意您無法使用相對 ../
路徑參照父項套件,而是要使用絕對路徑,例如 //data/regression:unittest/.
。
任何需要使用多個檔案的外部規則 (例如測試),都必須明確宣告對所有檔案的依附元件。您可以使用 filegroup()
,在 BUILD
檔案中將檔案分組:
filegroup(
name = 'my_data',
srcs = glob(['my_unittest_data/*'])
)
接著,您可以在測試中將標籤 my_data
參照為資料依附元件。
BUILD 檔案 | 瀏覽權限 |