如果 A
在建構或執行時間需要使用 B
,則目標 A
依附目標 B
。「依附」關係會使目標執行有向非循環圖 (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(); } |
|
現在,宣告的依附元件圖表現在可概略呈現實際依附元件,即使間接關閉也一樣;建構作業可能會失敗。
為解決這個問題,我們已在 BUILD
檔案中正確宣告從 a
到步驟 2 引入的 c
的實際依附元件。
依附元件類型
多數建構規則都有三個屬性,用於指定不同類型的一般依附元件: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
標籤做為資料依附元件。
建立檔案 | 瀏覽權限 |