您的建置有多少比例從快取載入取決於許多因素。在本節中,您將看到一些對於良好快取建置至關重要的工具。建置掃描是該工具鏈的一部分,並將在本指南中貫穿使用。
建置快取金鑰
建置快取中的成品由 建置快取金鑰 唯一識別。當啟用建置快取執行時,每個可快取的任務都會被分配一個建置快取金鑰,並用於將任務輸出載入和儲存到建置快取中。以下輸入會影響任務的建置快取金鑰
-
任務實作
-
任務動作實作
-
輸出屬性的名稱
-
任務輸入的名稱和值
如果兩個任務的相關建置快取金鑰相同,則它們可以透過使用建置快取來重用其輸出。
可重複的任務輸出
假設您的建置中包含一個程式碼產生器任務。當您有一個完全最新的建置,並且您清除並重新執行同一程式碼庫上的程式碼產生器任務時,它應該產生完全相同的輸出,因此任何依賴該輸出的內容都將保持最新。
也可能是您的程式碼產生器在其輸出中新增了一些額外資訊,這些資訊不依賴於其宣告的輸入,例如時間戳記。在這種情況下,重新執行任務將導致產生不同的程式碼(因為時間戳記將會更新)。依賴於程式碼產生器輸出的任務將需要重新執行。
當任務可快取時,任務輸出快取的本質確保了任務對於給定的一組輸入將具有相同的輸出。因此,可快取的任務應具有可重複的任務輸出。如果它們沒有,那麼執行任務和從快取載入任務的結果可能會有所不同,這可能會導致難以診斷的快取未命中。
在某些情況下,即使是備受信賴的工具也可能產生不可重複的輸出,並導致連鎖反應。一個例子是 Oracle 的 Java 編譯器,它 由於一個錯誤,根據要編譯的原始檔的順序,產生不同的位元組碼。如果您使用 Oracle JDK 8u31 或更早版本來編譯 buildSrc
子專案中的程式碼,這可能會導致您的所有自訂任務產生偶爾的快取未命中,因為它們的類別路徑(包括 buildSrc
)存在差異。
這裡的關鍵是可快取的任務不應使用不可重複的任務輸出作為輸入。
穩定的任務輸入
如果任務的輸入不斷變化,則任務可重複地產生相同的輸出是不夠的。這種不穩定的輸入可以直接提供給任務。考慮將包含時間戳記的版本號新增至 jar 檔案的 manifest
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
任務的輸入都會有所不同。
不穩定輸入的另一個範例是來自版本控制的 commit ID。也許您的版本號是透過 git describe
產生的(並且您將其包含在 jar manifest 中,如上所示)。或者,也許您直接將 commit hash 包含在 version.properties
或 jar manifest 屬性中。無論哪種方式,任何依賴於此類資料的任務產生的輸出只能由針對完全相同的 commit 執行的建置重用。
另一個常見但不太明顯的不穩定輸入來源是當任務使用另一個產生不可重複結果的任務的輸出時,例如之前在輸出中嵌入時間戳記的程式碼產生器的範例。
只有當任務具有穩定的任務輸入時,才能從快取載入任務。不穩定的任務輸入會導致任務在每次建置時都具有一組唯一的輸入,這將始終導致快取未命中。
透過輸入正規化提升重用性
擁有穩定的輸入對於可快取的任務至關重要。但是,對於每個任務實現位元組對位元組相同的輸入可能具有挑戰性。在某些情況下,清理任務的輸出以移除不必要的資訊可能是一個好的方法,但這也意味著任務的輸出只能針對單一目的進行正規化。
這就是 輸入正規化 發揮作用的地方。Gradle 使用輸入正規化來判斷兩個任務輸入是否本質上相同。Gradle 在執行最新檢查以及判斷是否可以重用快取結果而不是執行任務時,使用正規化的輸入。由於輸入正規化是由使用資料作為輸入的任務宣告的,因此不同的任務可以定義不同的方式來正規化相同的資料。
當涉及檔案輸入時,Gradle 可以正規化檔案的路徑及其內容。
路徑敏感度和可重定位性
在電腦之間共用快取結果時,很少有人從電腦上的完全相同的位置執行建置。為了允許即使從不同的根目錄執行建置時也能共用快取結果,Gradle 需要了解哪些輸入可以重定位,哪些不能。
將檔案作為輸入的任務可以宣告檔案路徑中對它們至關重要的部分:這稱為輸入的 路徑敏感度。以 ABSOLUTE
路徑敏感度宣告的任務屬性被認為是不可重定位的。這也是未宣告路徑敏感度的屬性的預設值。
例如,Java 編譯器產生的類別檔案取決於 Java 原始檔的檔名:重新命名其中包含 public 類別的原始檔將會使建置失敗。雖然移動檔案不會對編譯結果產生影響,但對於增量編譯,JavaCompile
任務依賴於相對路徑來尋找同一套件中的其他類別。因此,JavaCompile
任務來源的路徑敏感度為 RELATIVE
。因此,只有 Java 原始檔的正規化(相對)路徑被視為 JavaCompile
任務的輸入。
Java 編譯器僅遵循 Java 原始檔中的套件宣告,而不遵循來源的相對路徑。因此,Java 來源的路徑敏感度為 NAME_ONLY 而不是 RELATIVE 。 |
內容正規化
Java 的編譯避免
當涉及 JavaCompile
任務的相依性(即其編譯類別路徑)時,只有對這些相依性的應用程式二進制介面 (ABI) 的變更才需要執行編譯。Gradle 深入了解編譯類別路徑是什麼,並對其使用複雜的正規化策略。只要編譯類別路徑上的類別的 ABI 保持不變,任務輸出就可以重用。這使 Gradle 能夠透過使用增量建置或從快取載入由不同(但 ABI 相容)版本的相依性產生的結果來避免 Java 編譯。有關編譯避免的更多資訊,請參閱 相應章節。
執行階段類別路徑正規化
與編譯避免類似,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'
}
}
如果將此類檔案新增至 jar 檔案是您對建置中的所有專案都執行的操作,並且您想要為所有使用者篩選此檔案,則您可以將上述配置包裝在根建置腳本中的 allprojects {}
或 subprojects {}
區塊中。
此配置的效果是,對於最新檢查和任務輸出快取,將忽略對 build-info.properties
的變更。專案中已進行此配置的所有任務的所有執行階段類別路徑輸入都將受到影響。這不會變更 test
任務的執行階段行為 — 也就是說,任何測試仍然能夠載入 build-info.properties
,並且執行階段類別路徑與之前保持不變。
反對輸出重疊的情況
當兩個任務寫入相同的輸出目錄或輸出檔案時,Gradle 難以判斷哪個輸出屬於哪個任務。存在許多邊緣情況,並且無法安全地並行執行任務。出於同樣的原因,Gradle 無法移除這些任務的 過時輸出檔案。具有離散、非重疊輸出的任務始終可以由 Gradle 以安全的方式處理。由於上述原因,對於輸出目錄與另一個任務重疊的任務,會自動停用任務輸出快取。
建置掃描會在時間軸中顯示由於輸出重疊而停用快取的任務

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