簡介
組態快取是一項功能,透過快取組態階段的結果,並在後續組建中重複使用,大幅提升組建效能。使用組態快取時,如果影響組建組態的項目(例如組建腳本)沒有變更,Gradle 可以完全略過組態階段。Gradle 也會套用效能改善至任務執行。
組態快取在概念上類似於組建快取,但快取不同的資訊。組建快取負責快取組建的輸出和中間檔案,例如任務輸出或人工產出轉換輸出。組態快取負責快取特定任務組的組建組態。換句話說,組態快取會儲存組態階段的輸出,而組建快取會儲存執行階段的輸出。
此功能目前未預設啟用。此功能有下列限制
|
如何運作?
當組態快取已啟用,而您為特定工作集執行 Gradle 時,例如執行 gradlew check
,Gradle 會檢查是否有可用的組態快取項目,以供請求的工作集使用。如果有,Gradle 會使用此項目,而不是執行組態階段。快取項目包含有關要執行的任務集的資訊,以及其組態和相依性資訊。
您第一次執行特定工作集時,組態快取中不會有這些任務的項目,因此 Gradle 會正常執行組態階段
-
執行初始化指令碼。
-
執行組建的設定指令碼,套用任何要求的設定外掛。
-
如果存在,組態和組建
buildSrc
專案。 -
執行組建的組建指令碼,套用任何要求的專案外掛。
-
計算請求任務的任務圖,執行任何延遲組態動作。
在組態階段之後,Gradle 會將任務圖的快照寫入新的組態快取項目,以供以後的 Gradle 呼叫使用。然後,Gradle 會從組態快取載入任務圖,以便它可以對任務套用最佳化,然後正常執行執行階段。當您第一次執行特定工作集時,仍會花費組態時間。但是,您應該會立即看到組建效能提升,因為任務會並行執行。
當您稍後使用相同的任務集執行 Gradle 時,例如再次執行 gradlew check
,Gradle 會直接從組態快取載入任務及其組態,並完全略過組態階段。在使用組態快取項目之前,Gradle 會檢查項目的任何「組建組態輸入」,例如組建指令碼,是否已變更。如果組建組態輸入已變更,Gradle 將不會使用該項目,並會如上所述再次執行組態階段,儲存結果以供以後重複使用。
組建組態輸入包括
-
初始化腳本
-
設定腳本
-
建置腳本
-
組態階段使用的系統屬性
-
組態階段使用的 Gradle 屬性
-
組態階段使用的環境變數
-
使用值供應器(例如提供者)存取的組態檔案
-
buildSrc
和外掛包含的建置輸入,包括建置組態輸入和原始檔。
Gradle 使用自己的最佳化序列化機制和格式來儲存組態快取項目。它會自動序列化任意物件圖表的狀態。如果您的工作保留對具有簡單狀態或支援類型物件的參照,您不需要執行任何動作來支援序列化。
作為備援並提供一些協助以遷移現有的工作,Java 序列化 的一些語意受到支援。但建議不要依賴它,主要考量效能因素。
效能改善
除了略過組態階段之外,組態快取還提供一些額外的效能改善
-
預設所有工作都並行執行,但須符合相依性限制。
-
相依性解析已快取。
-
在寫入工作圖表後,組態狀態和相依性解析狀態會從堆疊中捨棄。這會減少執行特定工作集所需的堆疊使用量。
使用組態快取
建議從最簡單的工作呼叫開始。在啟用組態快取的情況下執行 help
是個不錯的第一步
❯ gradle --configuration-cache help Calculating task graph as no cached configuration is available for tasks: help ... BUILD SUCCESSFUL in 4s 1 actionable task: 1 executed Configuration cache entry stored.
第一次執行時,組態階段會執行,並計算工作圖表。
然後,再次執行相同的指令。這會重複使用快取的組態
❯ gradle --configuration-cache help Reusing configuration cache. ... BUILD SUCCESSFUL in 500ms 1 actionable task: 1 executed Configuration cache entry reused.
如果在您的建置中成功,恭喜您,現在您可以嘗試更多有用的工作。您應該鎖定您的開發迴圈。一個很好的範例是在進行增量變更後執行測試。
如果在快取或重複使用組態時發現任何問題,將會產生 HTML 報告,協助您診斷並修正問題。報告也會顯示在組態階段讀取到的偵測組建組態輸入,例如系統屬性、環境變數和值供應器。有關更多資訊,請參閱以下 疑難排解 區段。
請繼續閱讀,瞭解如何調整組態快取、在發生問題時手動取消狀態驗證,以及從 IDE 使用組態快取。
啟用組態快取
預設情況下,Gradle 不會使用組態快取。若要在組建時啟用快取,請使用 configuration-cache
旗標
❯ gradle --configuration-cache
您也可以使用 org.gradle.configuration-cache
屬性,在 gradle.properties
檔案中持續啟用快取
org.gradle.configuration-cache=true
如果在 gradle.properties
檔案中啟用,您可以覆寫該設定,並在組建時使用 no-configuration-cache
旗標停用快取
❯ gradle --no-configuration-cache
忽略問題
預設情況下,如果遇到任何組態快取問題,Gradle 會讓組建失敗。在逐步改善您的外掛或組建邏輯以支援組態快取時,將問題暫時轉換為警告會很有用,但無法保證組建會運作。
這可以從命令列執行
❯ gradle --configuration-cache-problems=warn
或在 gradle.properties
檔案中執行
org.gradle.configuration-cache.problems=warn
允許最大問題數
當組態快取問題轉換為警告時,如果預設發現 512
個問題,Gradle 會讓組建失敗。
這可以透過在命令列上指定允許的最大問題數來調整
❯ gradle -Dorg.gradle.configuration-cache.max-problems=5
或在 gradle.properties
檔案中執行
org.gradle.configuration-cache.max-problems=5
穩定的組態快取
為了穩定組態快取,我們在功能標記後方實作了一些嚴格性,因為它被認為對早期採用者來說過於具破壞性。
您可以如下啟用該功能標記
enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
enableFeaturePreview "STABLE_CONFIGURATION_CACHE"
STABLE_CONFIGURATION_CACHE
功能標記啟用下列功能
- 未宣告的共用建置服務使用
-
啟用時,使用 共用建置服務 的工作,如果未透過
Task.usesService
方法宣告需求,將發出不建議使用的警告。
此外,如果組態快取未啟用,但功能標記存在,也會啟用下列 組態快取需求 的不建議使用警告
建議盡快啟用它,以便在我們移除標記並將連結功能設為預設值時做好準備。
IDE 支援
如果您從 gradle.properties
檔案啟用並設定組態快取,則當您的 IDE 委派給 Gradle 時,組態快取將會啟用。無需執行其他操作。
gradle.properties
通常會簽入至原始碼控制。如果您不希望為整個團隊啟用組態快取,也可以只從您的 IDE 啟用組態快取,如下所述。
請注意,從 IDE 同步建置不會受益於組態快取,只有執行工作才會。
基於 IntelliJ 的 IDE
在 IntelliJ IDEA 或 Android Studio 中,這可以用兩種方式完成,可以是全域性的,也可以是針對每個執行組態。
若要為整個建置啟用,請前往 執行 > 編輯組態…
。這將開啟 IntelliJ IDEA 或 Android Studio 對話方塊,以設定執行/偵錯組態。選擇 範本 > Gradle
,並將必要的系統屬性新增至 VM 選項
欄位。
例如,若要啟用組態快取,將問題轉換為警告,請新增下列內容
-Dorg.gradle.configuration-cache=true -Dorg.gradle.configuration-cache.problems=warn
您也可以選擇僅針對特定執行組態啟用。在此情況下,請保持 範本 > Gradle
組態不變,並根據需要編輯每個執行組態。
結合這兩種方式,您可以在全球啟用,並停用某些執行組態,或相反。
您可以使用 gradle-idea-ext-plugin 從您的建置中設定 IntelliJ 執行組態。這是僅為 IDE 啟用組態快取的好方法。 |
Eclipse IDE
在 Eclipse IDE 中,您可以透過 Buildship 以兩種方式啟用並設定組態快取,可以是全球或每個執行組態。
若要全球啟用,請前往 喜好設定 > Gradle
。您可以使用上面描述的屬性作為系統屬性。例如,若要啟用組態快取,將問題轉換為警告,請新增下列 JVM 引數
-
-Dorg.gradle.configuration-cache=true
-
-Dorg.gradle.configuration-cache.problems=warn
若要為特定執行組態啟用,請前往 執行組態…
,找出您要變更的組態,前往 專案設定
,勾選 覆寫專案設定
核取方塊,並將相同的系統屬性新增為 JVM 引數
。
結合這兩種方式,您可以在全球啟用,並停用某些執行組態,或相反。
支援的外掛程式
組態快取是全新的,並為外掛程式實作引進新的需求。因此,核心 Gradle 外掛程式和社群外掛程式都需要調整。本節提供有關 核心 Gradle 外掛程式 和 社群外掛程式 中目前支援的資訊。
核心 Gradle 外掛程式
並非所有 核心 Gradle 外掛程式 都支援組態快取。
JVM 語言和架構 |
原生語言 |
封裝和發行 |
---|---|---|
程式碼分析 |
IDE 專案檔案產生 |
工具程式 |
✓ |
支援的外掛 |
⚠ |
部分支援的外掛 |
✖ |
不支援的外掛 |
社群外掛
請參閱問題 gradle/gradle#13490,以了解社群外掛的狀態。
疑難排解
以下各節將說明一些一般準則,說明如何處理組態快取的問題。這適用於您的建置邏輯和 Gradle 外掛。
無法序列化執行工作所需狀態時,會產生檢測到的問題 HTML 報告。Gradle 失敗輸出包含指向報告的可按一下連結。此報告很有用,可讓您深入探討問題,了解造成問題的原因。
我們來看一個包含幾個問題的簡單範例建置指令碼
tasks.register("someTask") {
val destination = System.getProperty("someDestination") (1)
inputs.dir("source")
outputs.dir(destination)
doLast {
project.copy { (2)
from("source")
into(destination)
}
}
}
tasks.register('someTask') {
def destination = System.getProperty('someDestination') (1)
inputs.dir('source')
outputs.dir(destination)
doLast {
project.copy { (2)
from 'source'
into destination
}
}
}
執行該工作會失敗,並在主控台中列印下列內容
❯ gradle --configuration-cache someTask -DsomeDestination=dest ... * What went wrong: Configuration cache problems found in this build. 1 problem was found storing the configuration cache. - Build file 'build.gradle': line 6: invocation of 'Task.project' at execution time is unsupported. See https://gradle-docs.dev.org.tw/0.0.0/userguide/configuration_cache.html#config_cache:requirements:use_project_during_execution See the complete report at file:///home/user/gradle/samples/build/reports/configuration-cache/<hash>/configuration-cache-report.html > Invocation of 'Task.project' by task ':someTask' at execution time is unsupported. * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. > Get more help at https://help.gradle.org. BUILD FAILED in 0s 1 actionable task: 1 executed Configuration cache entry discarded with 1 problem.
組態快取項目已捨棄,因為找到的問題導致建置失敗。
詳細資料可以在連結的 HTML 報告中找到

報告顯示問題集兩次。首先依問題訊息分組,然後依工作分組。前者讓您可以快速查看您的建置面臨哪些類別的問題。後者讓您可以快速查看哪些工作有問題。在這兩種情況下,您都可以展開樹狀結構,以找出物件圖中罪魁禍首的位置。
報告也包含檢測到的建置組態輸入清單,例如環境變數、系統屬性和組態階段讀取的值提供者

當變更您的建置或外掛以修正問題時,您應考慮 使用 TestKit 測試您的建置邏輯。 |
在此階段,您可以決定 將問題轉換為警告,並繼續探索您的建置如何回應組態快取,或修正目前的問題。
我們忽略報告的問題,並再次執行相同的建置兩次,看看在重複使用快取的有問題組態時會發生什麼事
❯ gradle --configuration-cache --configuration-cache-problems=warn someTask -DsomeDestination=dest Calculating task graph as no cached configuration is available for tasks: someTask > Task :someTask 1 problem was found storing the configuration cache. - Build file 'build.gradle': line 6: invocation of 'Task.project' at execution time is unsupported. See https://gradle-docs.dev.org.tw/0.0.0/userguide/configuration_cache.html#config_cache:requirements:use_project_during_execution See the complete report at file:///home/user/gradle/samples/build/reports/configuration-cache/<hash>/configuration-cache-report.html BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry stored with 1 problem. ❯ gradle --configuration-cache --configuration-cache-problems=warn someTask -DsomeDestination=dest Reusing configuration cache. > Task :someTask 1 problem was found reusing the configuration cache. - Build file 'build.gradle': line 6: invocation of 'Task.project' at execution time is unsupported. See https://gradle-docs.dev.org.tw/0.0.0/userguide/configuration_cache.html#config_cache:requirements:use_project_during_execution See the complete report at file:///home/user/gradle/samples/build/reports/configuration-cache/<hash>/configuration-cache-report.html BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry reused with 1 problem.
這兩個建置成功報告觀察到的問題,儲存然後重複使用組態快取。
在主控台問題摘要和 HTML 報告中存在的連結的協助下,我們可以修正我們的問題。以下是修正後的建置指令碼版本
abstract class MyCopyTask : DefaultTask() { (1)
@get:InputDirectory abstract val source: DirectoryProperty (2)
@get:OutputDirectory abstract val destination: DirectoryProperty (2)
@get:Inject abstract val fs: FileSystemOperations (3)
@TaskAction
fun action() {
fs.copy { (3)
from(source)
into(destination)
}
}
}
tasks.register<MyCopyTask>("someTask") {
val projectDir = layout.projectDirectory
source = projectDir.dir("source")
destination = projectDir.dir(System.getProperty("someDestination"))
}
abstract class MyCopyTask extends DefaultTask { (1)
@InputDirectory abstract DirectoryProperty getSource() (2)
@OutputDirectory abstract DirectoryProperty getDestination() (2)
@Inject abstract FileSystemOperations getFs() (3)
@TaskAction
void action() {
fs.copy { (3)
from source
into destination
}
}
}
tasks.register('someTask', MyCopyTask) {
def projectDir = layout.projectDirectory
source = projectDir.dir('source')
destination = projectDir.dir(System.getProperty('someDestination'))
}
1 | 我們將我們的臨時工作轉換為適當的工作類別, |
2 | 並宣告輸入和輸出, |
3 | 並注入 FileSystemOperations 服務,這是 project.copy {} 的支援替代方案。 |
現在執行工作兩次會成功,不會報告任何問題,並在第二次執行時重複使用組態快取
❯ gradle --configuration-cache someTask -DsomeDestination=dest Calculating task graph as no cached configuration is available for tasks: someTask > Task :someTask BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry stored. ❯ gradle --configuration-cache someTask -DsomeDestination=dest Reusing configuration cache. > Task :someTask BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry reused.
但是,如果我們變更系統屬性的值會怎麼樣?
❯ gradle --configuration-cache someTask -DsomeDestination=another Calculating task graph as configuration cache cannot be reused because system property 'someDestination' has changed. > Task :someTask BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry stored.
無法重複使用先前的組態快取項目,而且必須重新計算並儲存工作圖。這是因為我們在組態時間讀取系統屬性,因此當該屬性的值變更時,需要 Gradle 再次執行組態階段。只要取得系統屬性的提供者,並將它連接到工作輸入,而不需在組態時間讀取它,就能輕鬆修正這個問題。
tasks.register<MyCopyTask>("someTask") {
val projectDir = layout.projectDirectory
source = projectDir.dir("source")
destination = projectDir.dir(providers.systemProperty("someDestination")) (1)
}
tasks.register('someTask', MyCopyTask) {
def projectDir = layout.projectDirectory
source = projectDir.dir('source')
destination = projectDir.dir(providers.systemProperty('someDestination')) (1)
}
1 | 我們直接連接系統屬性提供者,而不需在組態時間讀取它。 |
只要進行這個簡單的變更,我們就能執行工作任意多次,變更系統屬性值,並重複使用組態快取
❯ gradle --configuration-cache someTask -DsomeDestination=dest Calculating task graph as no cached configuration is available for tasks: someTask > Task :someTask BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry stored. ❯ gradle --configuration-cache someTask -DsomeDestination=another Reusing configuration cache. > Task :someTask BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed Configuration cache entry reused.
我們現在已經修正這個簡單工作的問題。
繼續閱讀以瞭解如何為您的組建或外掛套用組態快取。
宣告與組態快取不相容的工作
可以透過 Task.notCompatibleWithConfigurationCache() 方法宣告特定工作與組態快取不相容。
在標示為不相容的工作中發現的組態快取問題將不再導致組建失敗。
而且,當不相容的工作排程執行時,Gradle 會在組建結束時捨棄組態狀態。您可以使用它來協助移轉,方法是暫時選擇退出某些難以變更為與組態快取搭配運作的工作。
查看方法文件以取得更多詳細資訊。
採用步驟
一個重要的先決條件是讓您的 Gradle 和外掛套用版本保持最新。以下是成功採用的建議步驟。它適用於組建和外掛套用。在執行這些步驟時,請記住 HTML 報告和下方 需求 章節中說明的解決方案。
- 從
:help
開始 -
務必先嘗試使用最簡單的工作
:help
來執行您的組建或外掛套用。這將執行您的組建或外掛套用的最小組態階段。 - 逐步鎖定有用的工作
-
不要馬上執行
build
。您也可以使用--dry-run
來先找出更多組態時間問題。在處理建置時,逐步針對您的開發回饋迴圈進行目標設定。例如,在對原始碼做出一些變更後執行測試。
在處理外掛時,逐步針對已貢獻或已設定的任務進行目標設定。
- 透過將問題轉換為警告來探索
-
不要在第一次建置失敗時就停止,並將問題轉換為警告,以了解您的建置和外掛的行為。如果建置失敗,請使用 HTML 報告來了解與失敗相關的已報告問題。繼續執行更多有用的任務。
這將讓您深入了解您的建置和外掛所面臨的問題性質。請記住,在將問題轉換為警告時,您可能需要手動使快取失效以解決問題。
- 退一步並反覆修正問題
-
當您覺得自己對需要修正的內容有足夠了解時,請退一步並開始反覆修正最重要的問題。使用 HTML 報告和此文件來協助您完成此行程。
從儲存設定快取時報告的問題開始。修正後,您可以依賴有效的快取設定階段,並繼續修正載入設定快取時報告的問題(如果有)。
- 報告遇到的問題
-
如果您遇到Gradle 功能或Gradle 核心外掛的問題,而此文件未涵蓋,請在
gradle/gradle
上報告問題。如果您遇到社群 Gradle 外掛的問題,請查看是否已在gradle/gradle#13490中列出,並考慮向外掛的議題追蹤器報告問題。
報告此類問題的一個好方法是提供以下資訊
-
指向此文件的連結,
-
您嘗試的外掛版本,
-
外掛的客製化設定(如果有),或理想情況下是可重製的建置,
-
失敗內容的說明,例如特定任務的問題
-
建置失敗的副本,
-
獨立的
configuration-cache-report.html
檔案。
-
- 測試、測試、測試
-
考慮為您的建置邏輯新增測試。請參閱以下關於測試您的建置邏輯的區段,以取得設定快取的資訊。這將有助於您在反覆處理所需的變更時,並防止將來的回歸。
- 向您的團隊推廣
-
一旦您的開發人員工作流程運作順利,例如從 IDE 執行測試,您就可以考慮為您的團隊啟用它。在變更程式碼和執行測試時,更快的周轉時間可能是值得的。您可能會想先將此設定為選用功能。
如果需要,將問題轉換為警告,並在您的建置
gradle.properties
檔案中設定允許問題的最大數量。預設情況下,請將設定快取停用。讓您的團隊知道他們可以選擇加入,例如,針對受支援的工作流程,在他們的 IDE 執行設定中啟用設定快取。稍後,當更多工作流程在運作時,您可以反轉此設定。預設啟用組態快取,設定 CI 以停用它,並在需要時傳達組態快取需要停用的不支援工作流程。
在建置中對組態快取做出反應
建置邏輯或外掛實作可以偵測組態快取是否已針對特定建置啟用,並對此做出相應反應。組態快取的active 狀態會提供在對應的建置功能中。您可以透過注入BuildFeatures
服務到您的程式碼中來存取它。
您可以使用此資訊以不同的方式設定外掛的功能,或停用尚未相容的選用功能。另一個範例包括為您的使用者提供其他指引,以防他們需要調整其設定或得知暫時限制。
採用組態快取行為的變更
Gradle 發行版本會為組態快取帶來增強功能,讓它偵測更多組態邏輯與環境互動的案例。這些變更透過消除潛在的錯誤快取命中,來改善快取的正確性。另一方面,它們會強制執行更嚴格的規則,讓外掛和建置邏輯需要遵循,才能盡可能經常快取。
如果組態輸入的結果不會影響已設定的任務,則其中一些組態輸入可能會被視為「良性」。因為它們而產生新的組態遺漏,對建置使用者來說可能是不可取的,而消除它們的建議策略是
-
透過組態快取報告找出導致組態快取失效的組態輸入。
-
修正專案建置邏輯存取的未宣告組態輸入。
-
向外掛維護人員回報由第三方外掛造成的議題,並在外掛修正後更新外掛。
-
-
對於某些類型的組態輸入,可以使用選擇退出選項,讓 Gradle 回復到較早的行為,從偵測中省略輸入。此暫時解決方法旨在減輕因過時外掛而產生的效能議題。
在下列情況下,可以暫時選擇不進行組態輸入偵測
-
自 Gradle 8.1 起,使用許多與檔案系統相關的 API 會正確追蹤為組態輸入,包括檔案系統檢查,例如 `File.exists()` 或 `File.isFile()`。
若要讓輸入追蹤忽略特定路徑上的這些檔案系統檢查,可以使用 Gradle 屬性 `org.gradle.configuration-cache.inputs.unsafe.ignore.file-system-checks`,其中包含路徑清單,相對於根專案目錄並以 `;` 分隔。若要忽略多個路徑,請使用 `*` 來比對單一區段內的任意字串,或使用 `**` 來比對跨區段的字串。以 `~/` 開頭的路徑會根據使用者家目錄為基礎。例如
gradle.propertiesorg.gradle.configuration-cache.inputs.unsafe.ignore.file-system-checks=\ ~/.third-party-plugin/*.lock;\ ../../externalOutputDirectory/**;\ build/analytics.json
-
在 Gradle 8.4 之前,組態邏輯中從未使用的部分未宣告組態輸入仍會在組態快取序列化任務圖時被讀取。不過,它們的變更之後不會使組態快取失效。從 Gradle 8.4 開始,此類未宣告的組態輸入會被正確追蹤。
若要暫時回復為較早的行為,請將 Gradle 屬性 `org.gradle.configuration-cache.inputs.unsafe.ignore.in-serialization` 設為 `true`。
請謹慎忽略組態輸入,而且僅在它們不會影響組態邏輯所產生的任務時才忽略。這些選項的支援會在未來的版本中移除。
測試您的組建邏輯
Gradle TestKit(又稱為 TestKit)是一個有助於測試 Gradle 外掛和組建邏輯的函式庫。有關如何使用 TestKit 的一般指南,請參閱專屬章節。
若要在測試中啟用組態快取,您可以將 `--configuration-cache` 引數傳遞給 GradleRunner,或使用 啟用組態快取中所述的其他方法之一。
您需要執行您的任務兩次。一次用於啟動組態快取。一次用於重複使用組態快取。
@Test
fun `my task can be loaded from the configuration cache`() {
buildFile.writeText("""
plugins {
id 'org.example.my-plugin'
}
""")
runner()
.withArguments("--configuration-cache", "myTask") (1)
.build()
val result = runner()
.withArguments("--configuration-cache", "myTask") (2)
.build()
require(result.output.contains("Reusing configuration cache.")) (3)
// ... more assertions on your task behavior
}
def "my task can be loaded from the configuration cache"() {
given:
buildFile << """
plugins {
id 'org.example.my-plugin'
}
"""
when:
runner()
.withArguments('--configuration-cache', 'myTask') (1)
.build()
and:
def result = runner()
.withArguments('--configuration-cache', 'myTask') (2)
.build()
then:
result.output.contains('Reusing configuration cache.') (3)
// ... more assertions on your task behavior
}
1 | 第一次執行會啟動組態快取。 |
2 | 第二次執行會重複使用組態快取。 |
3 | 斷言組態快取會被重複使用。 |
如果發現組態快取有問題,Gradle 會讓組建失敗並報告問題,而且測試也會失敗。
針對 Gradle 外掛的良好測試策略是使用啟用的組態快取來執行其整個測試套件。這需要使用受支援的 Gradle 版本來測試外掛。 如果外掛程式已支援多個 Gradle 版本,它可能已針對多個 Gradle 版本進行測試。在這種情況下,我們建議從支援它的 Gradle 版本開始啟用組態快取。 如果無法立即執行此操作,使用執行外掛程式所貢獻的所有工作數次,例如宣告 |
需求
為了將工作圖形狀態擷取到組態快取並在後續建置中重新載入,Gradle 會對工作和其他建置邏輯套用特定需求。每個需求都視為組態快取「問題」,如果存在違規,則建置會失敗。
這些需求大部分實際上會浮現一些未宣告的輸入。換句話說,使用組態快取是選擇加入所有建置的更嚴格性、正確性和可靠性。
下列各節說明每個需求以及如何變更您的建置以修正問題。
工作不得參照特定類型
有許多類型的工作實例不得從其欄位參照。doFirst {}
或 doLast {}
等閉包中的工作動作也適用。
這些類型分為以下幾類
-
即時 JVM 狀態類型
-
Gradle 模型類型
-
相依性管理類型
在所有情況下,這些類型不被允許的原因是組態快取無法輕易儲存或重新建立其狀態。
即時 JVM 狀態類型 (例如 ClassLoader
、Thread
、OutputStream
、Socket
等) 根本不被允許。這些類型幾乎從不代表工作輸入或輸出。唯一的例外是標準串流:System.in
、System.out
和 System.err
。這些串流可以用作參數,例如 Exec
和 JavaExec
工作。
Gradle 模型類型(例如 Gradle
、Settings
、Project
、SourceSet
、Configuration
等)通常用於傳遞一些任務輸入,這些輸入應明確且精確地宣告。
例如,如果您參照 Project
以在執行時間取得 project.version
,您應改為使用 Property<String>
將 專案版本 直接宣告為任務的輸入。另一個範例是參照 SourceSet
以稍後取得原始碼檔案、編譯類別路徑或原始碼組的輸出。您應改為將這些宣告為 FileCollection
輸入,並參照該輸入。
相同的需求也適用於依賴管理類型,但有些許差異。
某些類型,例如 Configuration
或 SourceDirectorySet
,並不適合作為任務輸入參數,因為它們包含許多不相關的狀態,而將這些輸入建模為更精確的內容會更好。我們完全不打算讓這些類型可序列化。例如,如果您參照 Configuration
以稍後取得已解析的檔案,您應改為宣告 FileCollection
作為任務的輸入。同樣地,如果您參照 SourceDirectorySet
,您應改為宣告 FileTree
作為任務的輸入。
參照依賴解析結果也不被允許(例如 ArtifactResolutionQuery
、ResolvedArtifact
、ArtifactResult
等)。例如,如果您參照某些 ResolvedComponentResult
執行個體,您應改為宣告 Provider<ResolvedComponentResult>
作為任務的輸入。此類提供者可透過呼叫 ResolutionResult.getRootComponent()
取得。同樣地,如果您參照某些 ResolvedArtifactResult
執行個體,您應改為使用 ArtifactCollection.getResolvedArtifacts()
,它會傳回 Provider<Set<ResolvedArtifactResult>>
,可對應為任務的輸入。經驗法則為,任務不得參照 已解析 的結果,而應參照延遲規格,以便在執行時間執行依賴解析。
某些類型,例如 Publication
或 Dependency
,不可序列化,但可以序列化。如有必要,我們可能會允許這些類型直接用作任務輸入。
以下是參照 SourceSet
的有問題任務類型範例
abstract class SomeTask : DefaultTask() {
@get:Input lateinit var sourceSet: SourceSet (1)
@TaskAction
fun action() {
val classpathFiles = sourceSet.compileClasspath.files
// ...
}
}
abstract class SomeTask extends DefaultTask {
@Input SourceSet sourceSet (1)
@TaskAction
void action() {
def classpathFiles = sourceSet.compileClasspath.files
// ...
}
}
1 | 這將報告為問題,因為參照 SourceSet 不被允許 |
以下是如何正確執行
abstract class SomeTask : DefaultTask() {
@get:InputFiles @get:Classpath
abstract val classpath: ConfigurableFileCollection (1)
@TaskAction
fun action() {
val classpathFiles = classpath.files
// ...
}
}
abstract class SomeTask extends DefaultTask {
@InputFiles @Classpath
abstract ConfigurableFileCollection getClasspath() (1)
@TaskAction
void action() {
def classpathFiles = classpath.files
// ...
}
}
1 | 不再報告問題,我們現在參照支援的類型 FileCollection |
同樣地,如果您在腳本中宣告自訂任務時遇到相同問題,如下所示
tasks.register("someTask") {
doLast {
val classpathFiles = sourceSets.main.get().compileClasspath.files (1)
}
}
tasks.register('someTask') {
doLast {
def classpathFiles = sourceSets.main.compileClasspath.files (1)
}
}
1 | 這將報告為問題,因為 doLast {} 閉包會擷取對 SourceSet 的參照 |
您仍需要滿足相同的需求,也就是不參照不允許的類型。以下是修復上述任務宣告的方式
tasks.register("someTask") {
val classpath = sourceSets.main.get().compileClasspath (1)
doLast {
val classpathFiles = classpath.files
}
}
tasks.register('someTask') {
def classpath = sourceSets.main.compileClasspath (1)
doLast {
def classpathFiles = classpath.files
}
}
1 | 不再報告問題,doLast {} 閉包現在只擷取支援的 FileCollection 類型的 classpath |
請注意,有時會間接參照不允許的類型。例如,您可以讓任務參照允許的附加元件中的一些類型。該類型可以參照另一個允許的類型,而該類型又參照不允許的類型。問題的 HTML 報告中提供的物件圖層級檢視應有助於您找出違規者。
使用 Project
物件
任務在執行時間不得使用任何 Project
物件。這包括在任務執行期間呼叫 Task.getProject()
。
有些情況可以與 不允許的類型 以相同的方式修復。
通常,Project
和 Task
上都會提供類似的事物。例如,如果您在任務動作中需要 Logger
,您應使用 Task.logger
,而不是 Project.logger
。
否則,您可以使用 注入的服務,而不是 Project
的方法。
以下是執行時間使用 Project
物件的有問題任務類型的範例
abstract class SomeTask : DefaultTask() {
@TaskAction
fun action() {
project.copy { (1)
from("source")
into("destination")
}
}
}
abstract class SomeTask extends DefaultTask {
@TaskAction
void action() {
project.copy { (1)
from 'source'
into 'destination'
}
}
}
1 | 這將報告為問題,因為任務動作在執行時間使用 Project 物件 |
以下是如何正確執行
abstract class SomeTask : DefaultTask() {
@get:Inject abstract val fs: FileSystemOperations (1)
@TaskAction
fun action() {
fs.copy {
from("source")
into("destination")
}
}
}
abstract class SomeTask extends DefaultTask {
@Inject abstract FileSystemOperations getFs() (1)
@TaskAction
void action() {
fs.copy {
from 'source'
into 'destination'
}
}
}
1 | 不再報告問題,注入的 FileSystemOperations 服務受支援,可取代 project.copy {} |
同樣地,如果您在腳本中宣告自訂任務時遇到相同問題,如下所示
tasks.register("someTask") {
doLast {
project.copy { (1)
from("source")
into("destination")
}
}
}
tasks.register('someTask') {
doLast {
project.copy { (1)
from 'source'
into 'destination'
}
}
}
1 | 這將報告為問題,因為任務動作在執行時間使用 Project 物件 |
以下是修復上述任務宣告的方式
interface Injected {
@get:Inject val fs: FileSystemOperations (1)
}
tasks.register("someTask") {
val injected = project.objects.newInstance<Injected>() (2)
doLast {
injected.fs.copy { (3)
from("source")
into("destination")
}
}
}
interface Injected {
@Inject FileSystemOperations getFs() (1)
}
tasks.register('someTask') {
def injected = project.objects.newInstance(Injected) (2)
doLast {
injected.fs.copy { (3)
from 'source'
into 'destination'
}
}
}
1 | 服務無法直接注入腳本,我們需要一個額外的類型來傳達注入點 |
2 | 在任務動作之外,使用 project.object 建立額外類型的執行個體 |
3 | 不再報告問題,任務動作參照提供 FileSystemOperations 服務的 injected ,受支援,可取代 project.copy {} |
如您在上面所見,修復在腳本中宣告的特殊任務需要相當繁瑣的儀式。現在是考慮將您的任務宣告萃取為適當任務類型的時機,如先前所示。
下表顯示哪些 API 或注入的服務應作為每個 Project
方法的取代。
取代 | 使用 |
---|---|
|
任務輸入或輸出屬性,或用於擷取使用 |
|
任務輸入或輸出屬性,或用於擷取使用 |
|
任務輸入或輸出屬性,或用於擷取使用 |
|
任務輸入或輸出屬性,或用於擷取使用 |
|
任務輸入或輸出屬性或腳本變數,用於擷取使用 |
|
任務輸入或輸出屬性或腳本變數,用於擷取使用 |
|
任務輸入或輸出屬性或腳本變數,用於擷取使用 |
|
|
|
|
|
|
|
任務輸入或輸出屬性或腳本變數,用於擷取使用 |
|
任務輸入或輸出屬性或腳本變數,用於擷取使用 |
|
|
|
|
|
|
|
|
|
|
|
任務輸入或輸出屬性或腳本變數,用於擷取使用 |
|
|
|
|
|
|
|
|
|
Kotlin、Groovy 或 Java API 可供您的建置邏輯使用。 |
|
|
|
|
|
|
|
共用可變動物件
將任務儲存到組態快取時,會序列化所有直接或間接透過任務欄位參照的物件。在大部分情況下,取消序列化會保留參照相等性:如果兩個欄位 a
和 b
在組態時間參照同一個執行個體,那麼在取消序列化後,它們會再次參照同一個執行個體,因此 a == b
(或在 Groovy 和 Kotlin 語法中為 a === b
)仍然成立。不過,由於效能考量,某些類別(特別是 java.lang.String
、java.io.File
,以及 java.util.Collection
介面的許多實作)會在序列化時不保留參照相等性。在取消序列化後,參照此類別物件的欄位可能會參照不同但相等的物件。
讓我們來看一個在任務欄位中儲存使用者定義物件和 ArrayList
的任務。
class StateObject {
// ...
}
abstract class StatefulTask : DefaultTask() {
@get:Internal
var stateObject: StateObject? = null
@get:Internal
var strings: List<String>? = null
}
tasks.register<StatefulTask>("checkEquality") {
val objectValue = StateObject()
val stringsValue = arrayListOf("a", "b")
stateObject = objectValue
strings = stringsValue
doLast { (1)
println("POJO reference equality: ${stateObject === objectValue}") (2)
println("Collection reference equality: ${strings === stringsValue}") (3)
println("Collection equality: ${strings == stringsValue}") (4)
}
}
class StateObject {
// ...
}
abstract class StatefulTask extends DefaultTask {
@Internal
StateObject stateObject
@Internal
List<String> strings
}
tasks.register("checkEquality", StatefulTask) {
def objectValue = new StateObject()
def stringsValue = ["a", "b"] as ArrayList<String>
stateObject = objectValue
strings = stringsValue
doLast { (1)
println("POJO reference equality: ${stateObject === objectValue}") (2)
println("Collection reference equality: ${strings === stringsValue}") (3)
println("Collection equality: ${strings == stringsValue}") (4)
}
}
1 | doLast 動作會擷取來自封閉範圍的參照。這些擷取的參照也會序列化到組態快取。 |
2 | 比較儲存在任務欄位中的使用者定義類別物件參照和在 doLast 動作中擷取的參照。 |
3 | 比較儲存在任務欄位中的 ArrayList 執行個體參照和在 doLast 動作中擷取的參照。 |
4 | 檢查儲存的清單和擷取的清單是否相等。 |
在沒有組態快取的情況下執行建置,結果顯示在兩種情況下都保留了參照相等性。
❯ gradle --no-configuration-cache checkEquality > Task :checkEquality POJO reference equality: true Collection reference equality: true Collection equality: true
不過,在啟用組態快取的情況下,只有使用者定義物件參照相同。清單參照不同,即使參照的清單相等。
❯ gradle --configuration-cache checkEquality > Task :checkEquality POJO reference equality: true Collection reference equality: false Collection equality: true
一般來說,不建議在組態和執行階段之間共用可變動物件。如果您需要這麼做,您應該始終將狀態包裝在您定義的類別中。無法保證標準 Java、Groovy 和 Kotlin 類型,或 Gradle 定義的類型的參照相等性會保留。
請注意,任務之間不會保留任何參照相等性:每個任務都是其自己的「領域」,因此無法在任務之間共用物件。您可以改用 建置服務 來包裝共用狀態。
存取任務擴充或慣例
任務不應在執行時間存取慣例和擴充,包括額外屬性。相反地,任何與任務執行相關的值都應建模為任務屬性。
使用建置監聽器
外掛程式和建置指令碼不得註冊任何建置監聽器。也就是說,在組態時間註冊並在執行時間收到通知的監聽器。例如 BuildListener
或 TaskExecutionListener
。
執行外部程序
外掛程式和建置指令碼應避免在組態時間執行外部程序。一般來說,最好在任務中執行外部程序,並正確宣告輸入和輸出,以避免在任務是最新的時候進行不必要的作業。如有必要,應僅使用與組態快取相容的 API,而不是 Java 和 Groovy 標準 API 或現有的 ExecOperations
、Project.exec
、Project.javaexec
,以及設定和初始化指令碼中的類似項目。對於較簡單的情況,當擷取程序的輸出就足夠時,可以使用 providers.exec() 和 providers.javaexec()
val gitVersion = providers.exec {
commandLine("git", "--version")
}.standardOutput.asText.get()
def gitVersion = providers.exec {
commandLine("git", "--version")
}.standardOutput.asText.get()
對於較複雜的情況,可以使用自訂的 ValueSource 實作,並注入 ExecOperations
。此 ExecOperations
執行個體可以在組態時間不受限制地使用。
abstract class GitVersionValueSource : ValueSource<String, ValueSourceParameters.None> {
@get:Inject
abstract val execOperations: ExecOperations
override fun obtain(): String {
val output = ByteArrayOutputStream()
execOperations.exec {
commandLine("git", "--version")
standardOutput = output
}
return String(output.toByteArray(), Charset.defaultCharset())
}
}
abstract class GitVersionValueSource implements ValueSource<String, ValueSourceParameters.None> {
@Inject
abstract ExecOperations getExecOperations()
String obtain() {
ByteArrayOutputStream output = new ByteArrayOutputStream()
execOperations.exec {
it.commandLine "git", "--version"
it.standardOutput = output
}
return new String(output.toByteArray(), Charset.defaultCharset())
}
}
然後可以使用 ValueSource
實作來建立具有 providers.of 的提供者
val gitVersionProvider = providers.of(GitVersionValueSource::class) {}
val gitVersion = gitVersionProvider.get()
def gitVersionProvider = providers.of(GitVersionValueSource.class) {}
def gitVersion = gitVersionProvider.get()
在這兩種方法中,如果在組態時間使用提供者的值,則它將成為建置組態輸入。外部程序將針對每個建置執行,以確定組態快取是否是最新的,因此建議僅在組態時間呼叫快速執行的程序。如果值變更,則快取會失效,並且程序將在本次建置中作為組態階段的一部分再次執行。
讀取系統屬性和環境變數
外掛程式和建置指令碼可以在組態時間直接使用標準 Java、Groovy 或 Kotlin API 或值供應器 API 讀取系統屬性和環境變數。這麼做會讓此類變數或屬性成為建置組態輸入,因此變更值會使組態快取失效。組態快取報告包含這些建置組態輸入的清單,以協助追蹤它們。
一般來說,您應該避免在組態時間讀取系統屬性和環境變數的值,以避免在值變更時快取遺漏。您可以將 providers.systemProperty() 或 providers.environmentVariable() 傳回的 Provider
連接到工作屬性。
某些存取模式可能會列舉所有環境變數或系統屬性(例如,呼叫 System.getenv().forEach()
或使用其 keySet()
的反覆運算器),這些模式不建議使用。在這種情況下,Gradle 無法找出哪些屬性是實際的建置組態輸入,因此每個可用的屬性都會變成一個輸入。如果使用此模式,即使新增新的屬性也會使快取失效。
使用自訂謂詞來篩選環境變數是此不建議模式的一個範例
val jdkLocations = System.getenv().filterKeys {
it.startsWith("JDK_")
}
def jdkLocations = System.getenv().findAll {
key, _ -> key.startsWith("JDK_")
}
謂詞中的邏輯對組態快取是不透明的,因此所有環境變數都被視為輸入。減少輸入數量的其中一種方法是永遠使用查詢具體變數名稱的方法,例如 getenv(String)
或 getenv().get()
val jdkVariables = listOf("JDK_8", "JDK_11", "JDK_17")
val jdkLocations = jdkVariables.filter { v ->
System.getenv(v) != null
}.associate { v ->
v to System.getenv(v)
}
def jdkVariables = ["JDK_8", "JDK_11", "JDK_17"]
def jdkLocations = jdkVariables.findAll { v ->
System.getenv(v) != null
}.collectEntries { v ->
[v, System.getenv(v)]
}
val jdkLocationsProvider = providers.environmentVariablesPrefixedBy("JDK_")
def jdkLocationsProvider = providers.environmentVariablesPrefixedBy("JDK_")
請注意,組態快取不僅會在變數值變更或變數移除時失效,還會在與匹配前綴相符的另一個變數新增到環境時失效。
對於更複雜的使用案例,可以使用自訂 ValueSource 實作。在 ValueSource
程式碼中參照的系統屬性和環境變數不會變成建置組態輸入,因此可以套用任何處理。相反地,ValueSource
的值會在每次建置執行時重新計算,而且只有在值變更時組態快取才會失效。例如,ValueSource
可用於取得所有名稱包含子字串 JDK
的環境變數
abstract class EnvVarsWithSubstringValueSource : ValueSource<Map<String, String>, EnvVarsWithSubstringValueSource.Parameters> {
interface Parameters : ValueSourceParameters {
val substring: Property<String>
}
override fun obtain(): Map<String, String> {
return System.getenv().filterKeys { key ->
key.contains(parameters.substring.get())
}
}
}
val jdkLocationsProvider = providers.of(EnvVarsWithSubstringValueSource::class) {
parameters {
substring = "JDK"
}
}
abstract class EnvVarsWithSubstringValueSource implements ValueSource<Map<String, String>, Parameters> {
interface Parameters extends ValueSourceParameters {
Property<String> getSubstring()
}
Map<String, String> obtain() {
return System.getenv().findAll { key, _ ->
key.contains(parameters.substring.get())
}
}
}
def jdkLocationsProvider = providers.of(EnvVarsWithSubstringValueSource.class) {
parameters {
substring = "JDK"
}
}
未宣告的檔案讀取
外掛程式和建置指令碼不應在組態時間使用 Java、Groovy 或 Kotlin API 直接讀取檔案。請改用值供應器 API 將檔案宣告為潛在的建置組態輸入。
此問題是由類似下列的建置邏輯所導致
val config = file("some.conf").readText()
def config = file('some.conf').text
若要修正此問題,請改用 providers.fileContents() 來讀取檔案
val config = providers.fileContents(layout.projectDirectory.file("some.conf"))
.asText
def config = providers.fileContents(layout.projectDirectory.file('some.conf'))
.asText
一般來說,您應避免在組態時間讀取檔案,以避免在檔案內容變更時使組態快取項目失效。您可以將 providers.fileContents() 傳回的 Provider
連接到工作屬性。
位元組碼修改和 Java 代理程式
為了偵測組態輸入,Gradle 會修改建置指令碼類別路徑上的類別位元組碼,例如外掛及其相依性。Gradle 會使用 Java 代理程式來修改位元組碼。由於位元組碼已變更或代理程式存在,某些函式庫的完整性自我檢查可能會失敗。
若要解決此問題,您可以將 Worker API 與類別載入器或處理程序隔離搭配使用,以封裝函式庫程式碼。Worker 的類別路徑位元組碼不會被修改,因此自我檢查應該會通過。當使用處理程序隔離時,Worker 動作會在沒有安裝 Gradle Java 代理程式的獨立 Worker 處理程序中執行。
在簡單的情況下,當函式庫也提供命令列進入點(public static void main()
方法)時,您也可以使用 JavaExec 工作來隔離函式庫。
憑證和機密的處理
組態快取目前沒有選項可以防止儲存用作輸入的機密,因此它們可能會出現在序列化組態快取項目中,而該項目預設儲存在專案目錄中的 .gradle/configuration-cache
下。
為了降低意外揭露的風險,Gradle 會加密組態快取。Gradle 會視需要透明地產生機器特定的機密金鑰,並將其快取在 GRADLE_USER_HOME
目錄下,並用它來加密專案特定快取中的資料。
若要進一步增強安全性,請務必
-
確保組態快取項目的存取安全;
-
利用
GRADLE_USER_HOME/gradle.properties
來儲存機密。該檔案的內容不屬於組態快取的一部分,只有其指紋屬於。如果您將機密儲存在該檔案中,則必須小心保護檔案內容的存取權限。
請參閱 gradle/gradle#22618。
透過 GRADLE_ENCRYPTION_KEY
環境變數提供加密金鑰
預設情況下,Gradle 會自動產生並管理加密金鑰,作為儲存在 GRADLE_USER_HOME
目錄下的 Java 金鑰儲存區。
對於不希望這樣做的環境(例如,當 GRADLE_USER_HOME
目錄在多部電腦間共用時),您可以在讀取或寫入快取組態資料時,透過 GRADLE_ENCRYPTION_KEY
環境變數提供 Gradle 要使用的確切加密金鑰。
您必須確保在多次 Gradle 執行期間持續提供相同的加密金鑰,否則 Gradle 將無法重複使用現有的快取組態。 |
產生與 GRADLE_ENCRYPTION_KEY 相容的加密金鑰
若要讓 Gradle 使用使用者指定的加密金鑰加密組態快取,您必須在設定 GRADLE_ENCRYPTION_KEY 環境變數為有效的 AES 金鑰(編碼為 Base64 字串)的情況下執行 Gradle。
產生 Base64 編碼、AES 相容金鑰的方法之一是使用類似這樣的指令
❯ openssl rand -base64 16
這個指令應該可以在 Linux、Mac OS 或 Windows 上執行,如果使用的是 Cygwin 等工具的話。
然後您可以使用該指令產生的 Base64 編碼金鑰,並將其設定為 GRADLE_ENCRYPTION_KEY
環境變數的值。
尚未實作
尚未實作支援使用組態快取搭配特定 Gradle 功能。這些功能的支援將會在後續的 Gradle 版本中加入。
使用 Java 代理執行 TestKit 執行建置
使用 TestKit 執行建置時,組態快取可能會干擾套用至這些建置的 Java 代理程式,例如 Jacoco 代理程式。
請參閱 gradle/gradle#25979。
細部追蹤 Gradle 屬性作為建置組態輸入
目前,所有 Gradle 屬性的外部來源(專案目錄和 GRADLE_USER_HOME
中的 gradle.properties
、設定屬性的環境變數和系統屬性,以及使用命令列旗標指定的屬性)都視為建置組態輸入,而不管在組態時間實際使用哪些屬性。不過,這些來源並未包含在組態快取報告中。
請參閱 gradle/gradle#20969。
Java 物件序列化
Gradle 允許支援 Java 物件序列化 協定的物件儲存在組態快取中。
目前實作僅限於實作 java.io.Serializable
介面並定義下列方法組合之一的可序列化類別
-
writeObject
方法結合readObject
方法,以精確控制要儲存哪些資訊; -
writeObject
方法沒有對應的readObject
;writeObject
最終必須呼叫ObjectOutputStream.defaultWriteObject
; -
readObject
方法沒有對應的writeObject
;readObject
最終必須呼叫ObjectInputStream.defaultReadObject
; -
writeReplace
方法,允許類別指定要寫入的替換項; -
readResolve
方法,允許類別指定剛讀取的物件的替換項;
不支援下列 Java 物件序列化 功能
-
實作
java.io.Externalizable
介面的可序列化類別;組態快取會在序列化期間捨棄此類別的物件,並將其報告為問題; -
serialPersistentFields
成員,用於明確宣告哪些欄位可序列化;如果存在此成員,則會忽略它;組態快取會將所有欄位視為可序列化,但transient
欄位除外; -
ObjectOutputStream
的下列方法不受支援,且會擲回UnsupportedOperationException
-
reset()
、writeFields()
、putFields()
、writeChars(String)
、writeBytes(String)
和writeUnshared(Any?)
。
-
-
ObjectInputStream
的下列方法不受支援,且會擲回UnsupportedOperationException
-
readLine()
、readFully(ByteArray)
、readFully(ByteArray, Int, Int)
、readUnshared()
、readFields()
、transferTo(OutputStream)
和readAllBytes()
。
-
-
透過
ObjectInputStream.registerValidation
註冊的驗證會被忽略; -
如果存在
readObjectNoData
方法,則永遠不會呼叫;
請參閱 gradle/gradle#13588。
在執行時間存取建置指令碼的頂層方法和變數
重複使用建置指令碼中的邏輯和資料的常見方法是將重複的位元抽取到頂層方法和變數中。但是,如果啟用組態快取,則目前不支援在執行時間呼叫此類方法。
對於以 Groovy 編寫的建置指令碼,任務會失敗,因為找不到方法。下列程式碼片段在 listFiles
任務中使用頂層方法
def dir = file('data')
def listFiles(File dir) {
dir.listFiles({ file -> file.isFile() } as FileFilter).name.sort()
}
tasks.register('listFiles') {
doLast {
println listFiles(dir)
}
}
在啟用組態快取的情況下執行任務會產生下列錯誤
Execution failed for task ':listFiles'. > Could not find method listFiles() for arguments [/home/user/gradle/samples/data] on task ':listFiles' of type org.gradle.api.DefaultTask.
若要避免任務失敗,請將引用的頂層方法轉換為類別中的靜態方法
def dir = file('data')
class Files {
static def listFiles(File dir) {
dir.listFiles({ file -> file.isFile() } as FileFilter).name.sort()
}
}
tasks.register('listFilesFixed') {
doLast {
println Files.listFiles(dir)
}
}
以 Kotlin 編寫的建置指令碼無法將在執行時間引述頂層方法或變數的任務儲存在組態快取中。此限制存在的原因是無法序列化擷取的指令碼物件引述。Kotlin 版本的 listFiles
任務的第一次執行會因組態快取問題而失敗。
val dir = file("data")
fun listFiles(dir: File): List<String> =
dir.listFiles { file: File -> file.isFile }.map { it.name }.sorted()
tasks.register("listFiles") {
doLast {
println(listFiles(dir))
}
}
若要讓 Kotlin 版本的此任務與組態快取相容,請進行下列變更
object Files { (1)
fun listFiles(dir: File): List<String> =
dir.listFiles { file: File -> file.isFile }.map { it.name }.sorted()
}
tasks.register("listFilesFixed") {
val dir = file("data") (2)
doLast {
println(Files.listFiles(dir))
}
}
1 | 在物件內定義方法。 |
2 | 在較小的範圍內定義變數。 |
請參閱 gradle/gradle#22879。
使用建置服務使組態快取失效
目前,如果在組態時間存取 ValueSource
的值,則無法使用 BuildServiceProvider
或從中衍生的提供者搭配 map
或 flatMap
作為 ValueSource
的參數。當在組態階段執行任務中取得此類 ValueSource
時,也會套用相同的限制,例如 buildSrc
建置的任務或包含建置外掛程式的建置。請注意,使用 @ServiceReference
或將 BuildServiceProvider
儲存在任務的 @Internal
註解屬性中是安全的。一般來說,此限制會導致無法使用 BuildService
使組態快取失效。
請參閱 gradle/gradle#24085。