如果 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
的實際依附元件已在 BUILD
檔案中正確宣告。
依附元件類型
大多數的建構規則都有三個屬性,可用來指定不同類型的泛型依附元件:srcs
、deps
和 data
。以下說明這些選項。詳情請參閱「所有規則的共同屬性」。
許多規則也有其他屬性,用於規則專屬的依附元件類型,例如 compiler
或 resources
。詳情請參閱Build Encyclopedia。
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 檔案 | 可見度 |