從快取載入多少建置取決於許多因素。在本節中,您將看到一些對於快取良好的建置至關重要的工具。建置掃描是該工具鏈的一部分,將在整個指南中使用。
建置快取金鑰
建置快取中的成品會透過建置快取金鑰進行唯一識別。當在啟用建置快取的情況下執行時,會將建置快取金鑰指定給每個可快取的任務,並用於將任務輸出載入和儲存到建置快取。下列輸入會貢獻給任務的建置快取金鑰
-
任務實作
-
任務動作實作
-
輸出屬性的名稱
-
任務輸入的名稱和值
如果相關聯的建置快取金鑰相同,兩個任務就能透過建置快取重複使用其輸出。
可重複的任務輸出
假設您在建置中有一個程式碼產生器任務。當您有完全最新的建置,並且您清除並在相同的程式碼庫上重新執行程式碼產生器任務時,它應該產生完全相同的輸出,因此任何依賴於該輸出的內容都將保持最新。
您的程式碼產生器也可能會在其輸出中新增一些額外的資訊,而這些資訊並非取決於其宣告的輸入,例如時間戳記。在這種情況下,重新執行任務將導致產生不同的程式碼(因為時間戳記將會更新)。依賴於程式碼產生器輸出的任務將需要重新執行。
當任務可快取時,任務輸出快取的本質可確保任務對給定的一組輸入具有相同的輸出。因此,可快取任務應具有可重複的任務輸出。如果沒有,則執行任務和從快取載入任務的結果可能不同,這可能會導致難以診斷的快取未命中。
在某些情況下,即使是值得信賴的工具也可能產生不可重複的輸出,並導致連鎖效應。一個範例是 Oracle 的 Java 編譯器,由於錯誤,會根據提供給它的編譯原始檔順序產生不同的位元組碼。如果您使用 Oracle JDK 8u31 或更早版本編譯 buildSrc
子專案中的程式碼,這可能會導致所有自訂任務偶爾產生快取未命中,因為它們的類別路徑不同(包括 buildSrc
)。
這裡的重點是可快取任務不應使用不可重複的任務輸出作為輸入。
穩定的任務輸入
如果任務的輸入不斷變動,則任務重複產生相同輸出是不夠的。此類不穩定的輸入可以直接提供給任務。考慮在 jar 檔案的清單中加入包含時間戳記的版本號碼
version = "3.2-${System.currentTimeMillis()}"
tasks.jar {
manifest {
attributes(mapOf("Implementation-Version" to project.version))
}
}
version = "3.2-${System.currentTimeMillis()}"
tasks.named('jar') {
manifest {
attributes('Implementation-Version': project.version)
}
}
在上述範例中,jar
任務的輸入對於每次建置執行都會不同,因為這個時間戳記會持續變動。
不穩定輸入的另一個範例是版本控制中的提交 ID。您的版本號碼可能是透過 git describe
產生(而且您將它包含在 jar 清單中,如上所示)。或者您可能將提交雜湊值直接包含在 version.properties
或 jar 清單屬性中。無論哪一種方式,依賴此類資料的任何任務所產生的輸出都只能在針對完全相同的提交執行的建置中重複使用。
另一個常見但不太明顯的不穩定輸入來源是當任務使用產生不可重複結果的另一個任務的輸出時,例如前面程式碼產生器將時間戳記嵌入其輸出的範例。
只有當任務具有穩定的任務輸入時,才能從快取載入任務。不穩定的任務輸入會導致任務對於每個建置都有一組唯一的輸入,這將永遠導致快取未命中。
透過輸入正規化更佳的重複使用
對於可快取的任務而言,擁有穩定的輸入至關重要。但是,對於每個任務達成位元組對位元組相同的輸入可能具有挑戰性。在某些情況下,清除任務的輸出以移除不必要資訊可能是個好方法,但這也表示任務的輸出只能針對單一目的進行正規化。
這是 輸入正規化 發揮作用的地方。輸入正規化由 Gradle 使用,以判斷兩個任務輸入是否基本上相同。Gradle 在執行最新檢查時以及在判斷快取結果是否可重複使用(而不是執行任務)時,會使用正規化輸入。由於輸入正規化是由將資料視為輸入的任務消耗所宣告,因此不同的任務可以定義不同的方法來正規化相同的資料。
當涉及到檔案輸入時,Gradle 可以正規化檔案的路徑以及其內容。
路徑敏感度和可重新定位性
在電腦之間共用快取結果時,很少有人會在電腦上從完全相同的位置執行建置。為了讓快取結果能在從不同根目錄執行建置時共用,Gradle 需要了解哪些輸入可以重新定位,哪些不行。
具有檔案作為輸入的任務可以宣告檔案路徑的哪些部分對它們至關重要:這稱為輸入的 路徑敏感度。使用 ABSOLUTE
路徑敏感度宣告的任務屬性被視為不可重新定位。這也是未宣告路徑敏感度的屬性的預設值。
例如,Java 編譯器產生的類別檔案取決於 Java 原始檔的檔名:重新命名其中包含公開類別的原始檔會導致建置失敗。儘管移動檔案不會對編譯結果產生影響,但對於增量編譯,JavaCompile
任務依賴於相對路徑才能在同一個套件中找到其他類別。因此,JavaCompile
任務的原始檔的路徑敏感度為 RELATIVE
。因此,只有 Java 原始檔的正規化(相對)路徑才會被視為 JavaCompile
任務的輸入。
Java 編譯器只會尊重 Java 原始檔中的套件宣告,而不是原始檔的相對路徑。因此,Java 原始檔的路徑敏感度為 NAME_ONLY ,而不是 RELATIVE 。
|
內容正規化
Java 的編譯避免
在涉及 JavaCompile
任務的相依項時(也就是其編譯類別路徑),只有這些相依項的應用程式二進制介面 (ABI) 發生變更時,才需要執行編譯。Gradle 深入了解編譯類別路徑是什麼,並對其使用精密的正規化策略。只要編譯類別路徑上類別的 ABI 保持不變,就可以重複使用任務輸出。這讓 Gradle 能夠透過使用增量建置來避免 Java 編譯,或從快取中載入由不同(但 ABI 相容)版本的相依項所產生的結果。如需有關避免編譯的更多資訊,請參閱對應章節。
執行時期類別路徑正規化
類似於避免編譯,Gradle 也了解執行時期類別路徑的概念,並使用客製化輸入正規化來避免執行例如測試。對於執行時期類別路徑,Gradle 會檢查 jar 檔案的內容,並忽略 jar 檔案中條目的時間戳記和順序。這表示重建的 jar 檔案會被視為相同的執行時期類別路徑輸入。有關 Gradle 在偵測類別路徑變更時具備哪種程度的了解,以及什麼被視為類別路徑的詳細資訊,請參閱此章節。
篩選執行時期類別路徑
對於執行時期類別路徑,可以透過設定輸入正規化,讓 Gradle 更深入了解哪些檔案對輸入至關重要。
假設您想要將檔案 build-info.properties
新增到所有產生的 jar 檔案中,其中包含有關建置的變動資訊,例如建置開始時的時間戳記或用於識別發布成品的 CI 工作的 ID。此檔案僅用於稽核目的,不會影響執行測試的結果。儘管如此,此檔案還是 test
任務的執行時期類別路徑的一部分。由於檔案會在每次建置呼叫時變更,因此無法有效快取測試。若要修正此問題,您可以透過在使用專案的建置指令碼中新增下列設定,來忽略任何執行時期類別路徑上的 build-info.properties
normalization {
runtimeClasspath {
ignore("build-info.properties")
}
}
normalization {
runtimeClasspath {
ignore 'build-info.properties'
}
}
如果您會在建置中的所有專案新增此類檔案,而且想要為所有使用者篩選此檔案,您可以在根建置指令碼中將上述設定包覆在 allprojects {}
或 subprojects {}
區塊中。
此設定的效果是,對於已更新檢查和任務輸出快取,都會忽略 build-info.properties
的變更。所有執行此設定的專案中所有任務的所有執行時期類別路徑輸入都會受到影響。這不會變更 test
任務的執行時期行為,也就是說任何測試仍能夠載入 build-info.properties
,而執行時期類別路徑會保持與之前相同。
重疊輸出案例
當兩個工作寫入同一個輸出目錄或輸出檔案時,Gradle 很難判斷哪個輸出屬於哪個工作。有很多邊界案例,而且無法安全地並行執行工作。出於相同原因,Gradle 無法移除這些工作的過時輸出檔案。擁有離散、不重疊輸出的工作,Gradle 永遠都能安全地處理。由於上述原因,對於輸出目錄與另一個工作重疊的工作,會自動停用工作輸出快取。
建置掃描會在時間軸中顯示因輸出重疊而停用快取的工作

不同工作之間的輸出重複使用
有些建置會展現一個令人驚訝的特徵:即使針對一個空的快取執行,它們也會產生從快取載入的工作。這怎麼可能?請放心,這是完全正常的。
在考量工作輸出時,Gradle 只關心工作的輸入:工作類型本身、輸入檔案和參數等,但它不關心工作的名稱或可以在哪個專案中找到它。執行 `javac` 會產生相同的輸出,而不管呼叫它的 `JavaCompile` 工作的名稱為何。如果您的建置包含兩個共用每個輸入的工作,稍後執行的其中一個將能夠重複使用第一個產生的輸出。
在同一個建置中擁有兩個執行相同工作的任務聽起來像是一個需要修正的問題,但這不一定是壞事。例如,Android 外掛會為專案的每個變體建立多個工作;其中一些工作可能會執行相同的工作。這些工作可以安全地重複使用彼此的輸出。
如先前討論,您可以使用 Develocity 來診斷這些意外快取命中來源的建置。
不可快取的工作
您已經看過很多關於可快取工作的事,這表示也有不可快取的工作。如果快取工作輸出聽起來很棒,為什麼不快取每個工作?
有些工作絕對值得快取:執行複雜、可重複處理並產生中等量輸出的工作。編譯工作通常是快取的理想候選者。在光譜的另一端是 I/O 密集型工作,例如 `Copy` 和 `Sync`。在本地移動檔案通常無法透過從快取複製它們來加速。快取這些工作甚至會浪費良好的資源,因為會將所有這些重複的結果儲存在快取中。
大多數任務都顯然值得快取,或顯然不值得。對於介於兩者之間的任務,一個好的經驗法則就是看看下載結果是否會比在本地產生結果快得多。