概觀

Gradle *建置快取* 是一種快取機制,旨在透過重複使用其他建置產生的輸出,來節省時間。建置快取的工作原理是儲存(在本機或遠端)建置輸出,並允許建置在判斷輸入未變更時,從快取中提取這些輸出,從而避免重新產生它們的昂貴工作。

使用建置快取的第一個功能是 *任務輸出快取*。 本質上,任務輸出快取利用了與 最新檢查 相同的智慧,Gradle 使用最新檢查來避免在先前的本機建置已經產生一組任務輸出時執行工作。 但是,任務輸出快取不僅限於同一個工作區中的先前建置,還允許 Gradle 重複使用本機上任何位置的任何先前建置的任務輸出。 當使用共用建置快取進行任務輸出快取時,這甚至可以在開發人員機器和建置代理程式之間運作。

除了任務之外,構件轉換 也可以利用建置快取,並以類似於任務輸出快取的方式重複使用其輸出。

為了以實作方式學習如何使用建置快取,請先閱讀 建置快取的使用案例 以及後續章節。 它涵蓋了快取可以改善的不同情境,並詳細討論了在為建置啟用快取時需要注意的不同注意事項。

啟用建置快取

預設情況下,建置快取未啟用。 您可以透過幾種方式啟用建置快取

在命令列中使用 --build-cache 執行

Gradle 將僅針對此建置使用建置快取。

org.gradle.caching=true 放入您的 gradle.properties

Gradle 將嘗試重複使用先前建置的輸出,除非使用 --no-build-cache 明確停用。

啟用建置快取後,它會將建置輸出儲存在 Gradle 使用者首頁中。 有關配置此目錄或不同類型的建置快取,請參閱配置建置快取

任務輸出快取

除了 最新檢查 中描述的增量建置之外,Gradle 還可以透過比對任務的輸入,來重複使用先前任務執行的輸出,從而節省時間。 任務輸出可以在同一部電腦上的建置之間重複使用,甚至可以透過建置快取在不同電腦上運行的建置之間重複使用。

我們專注於使用者擁有組織範圍的遠端建置快取的使用案例,該快取由持續整合建置定期填充。 開發人員和其他持續整合代理程式應從遠端建置快取載入快取項目。 我們預期不允許開發人員填充遠端建置快取,並且所有持續整合建置在執行 clean 任務後填充建置快取。

為了讓您的建置與任務輸出快取良好協同運作,它必須與 增量建置 功能良好協同運作。 例如,當連續執行建置兩次時,所有具有輸出的任務都應為 UP-TO-DATE。 當不滿足此先決條件時,您不能期望在啟用任務輸出快取時獲得更快的建置或正確的建置。

當您啟用建置快取時,任務輸出快取會自動啟用,請參閱啟用建置快取

它看起來像什麼

讓我們從一個使用 Java 外掛且有一些 Java 源檔案的專案開始。 我們第一次執行建置。

> gradle --build-cache compileJava
:compileJava
:processResources
:classes
:jar
:assemble

BUILD SUCCESSFUL

我們在輸出中看到本機建置快取使用的目錄。 除此之外,建置與沒有建置快取時相同。 讓我們清理並再次執行建置。

> gradle clean
:clean

BUILD SUCCESSFUL
> gradle --build-cache assemble
:compileJava FROM-CACHE
:processResources
:classes
:jar
:assemble

BUILD SUCCESSFUL

現在我們看到,任務輸出已從建置快取載入,而不是執行 :compileJava 任務。 其他任務未從建置快取載入,因為它們不可快取。 這是因為 :classes:assemble生命週期任務,而 :processResources:jar 是類似 Copy 的任務,它們不可快取,因為通常執行它們會更快。

可快取任務

由於任務描述了其所有輸入和輸出,因此 Gradle 可以計算 *建置快取金鑰*,該金鑰根據任務的輸入唯一地定義任務的輸出。 該建置快取金鑰用於從建置快取請求先前的輸出,或將新的輸出儲存在建置快取中。 如果先前的建置輸出已由其他人(例如,您的持續整合伺服器或其他開發人員)儲存在快取中,您可以避免在本機執行大多數任務。

以下輸入以與 最新檢查 相同的方式,有助於任務的建置快取金鑰

  • 任務類型及其類別路徑

  • 輸出屬性的名稱

  • 名為「自訂任務類型」的章節 中描述的方式註解的屬性的名稱和值

  • 透過 DSL 透過 TaskInputs 添加的屬性的名稱和值

  • Gradle 發行版本、buildSrc 和外掛的類別路徑

  • 當建置腳本的內容影響任務執行時

任務類型需要使用 @CacheableTask 註解選擇加入任務輸出快取。 請注意,@CacheableTask 不會被子類別繼承。 預設情況下,自訂任務類型 *不可* 快取。

內建可快取任務

目前,以下內建 Gradle 任務可快取

目前所有其他內建任務都不可快取。

某些任務(例如 CopyJar)通常不適合設為可快取,因為 Gradle 只是將檔案從一個位置複製到另一個位置。 將不產生輸出或沒有任務動作的任務設為可快取也沒有意義。

第三方外掛

有些第三方外掛可以與建置快取良好協同運作。 最著名的範例是 Android 外掛 3.1+Kotlin 外掛 1.2.21+。 對於其他第三方外掛,請查看其文件以了解它們是否支援建置快取。

宣告任務輸入和輸出

可快取任務完整了解其輸入和輸出非常重要,以便可以安全地在其他地方重複使用一個建置的結果。

遺失任務輸入可能會導致不正確的快取命中,其中不同的結果被視為相同,因為兩個執行使用相同的快取金鑰。 如果 Gradle 未完全擷取給定任務的所有輸出,則遺失任務輸出可能會導致建置失敗。 錯誤宣告的任務輸入可能會導致快取未命中,尤其是在包含揮發性資料或絕對路徑時。 (請參閱 名為「任務輸入和輸出」的章節,了解應宣告為輸入和輸出的內容。)

任務路徑 *不是* 建置快取金鑰的輸入。 這表示具有不同任務路徑的任務可以重複使用彼此的輸出,只要 Gradle 確定執行它們會產生相同的結果即可。

為了確保正確宣告輸入和輸出,請使用整合測試(例如使用 TestKit)來檢查任務是否為相同的輸入產生相同的輸出,並擷取任務的所有輸出檔案。 我們建議新增測試以確保任務輸入是可重新定位的,即任務可以從快取載入到不同的建置目錄中(請參閱 @PathSensitive)。

為了處理任務的揮發性輸入,請考慮配置輸入正規化

預設將任務標記為不可快取

有些任務無法從使用建置快取中獲益。 一個範例是僅在檔案系統周圍移動資料的任務,例如 Copy 任務。 您可以透過將 @DisableCachingByDefault 註解新增至任務,來表示任務不可快取。 您也可以提供人類可讀的原因來說明預設情況下不快取任務。 註解可以單獨使用,也可以與 @CacheableTask 一起使用。

此註解僅用於記錄預設情況下不快取任務的原因。 建置邏輯可以透過執行階段 API 覆寫此決策(請參閱下文)。

啟用不可快取任務的快取

如我們所見,如果內建任務或外掛提供的任務的類別使用 Cacheable 註解進行註解,則這些任務是可快取的。 但是,如果您想讓類別不可快取的任務可快取,該怎麼辦? 讓我們以一個具體的範例來說明:您的建置腳本使用通用 NpmTask 任務,透過委派給 NPM(並運行 npm run bundle)來建立 JavaScript 捆綁包。 此過程類似於複雜的編譯任務,但 NpmTask 太過通用,無法預設為可快取:它僅採用引數並使用這些引數運行 npm。

此任務的輸入和輸出很容易計算出來。 輸入是包含 JavaScript 檔案和 NPM 配置檔案的目錄。 輸出是此任務產生的捆綁包檔案。

使用註解

我們建立 NpmTask 的子類別,並使用 註解來宣告輸入和輸出

在可能的情況下,最好使用委派而不是建立子類別。 內建的 JavaExecExecCopySync 任務就是這種情況,它們在 Project 上有一個方法來執行實際工作。

如果您是現代 JavaScript 開發人員,您會知道捆綁可能相當耗時,並且值得快取。 為了實現這一點,我們需要告知 Gradle 允許快取該任務的輸出,方法是使用 @CacheableTask 註解。

這足以使任務在您自己的機器上可快取。 但是,輸入檔案預設由其絕對路徑識別。 因此,如果快取需要在使用不同路徑的多個開發人員或機器之間共用,則這將無法如預期般運作。 因此,我們還需要設定 路徑敏感度。 在這種情況下,可以使用輸入檔案的相對路徑來識別它們。

請注意,可以透過覆寫基底類別的 getter 並註解該方法,來覆寫基底類別的屬性註解。

build.gradle.kts
@CacheableTask                                       (1)
abstract class BundleTask : NpmTask() {

    @get:Internal                                    (2)
    override val args
        get() = super.args


    @get:InputDirectory
    @get:SkipWhenEmpty
    @get:PathSensitive(PathSensitivity.RELATIVE)     (3)
    abstract val scripts: DirectoryProperty

    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)     (4)
    abstract val configFiles: ConfigurableFileCollection

    @get:OutputFile
    abstract val bundle: RegularFileProperty

    init {
        args.addAll("run", "bundle")
        bundle = projectLayout.buildDirectory.file("bundle.js")
        scripts = projectLayout.projectDirectory.dir("scripts")
        configFiles.from(projectLayout.projectDirectory.file("package.json"))
        configFiles.from(projectLayout.projectDirectory.file("package-lock.json"))
    }
}

tasks.register<BundleTask>("bundle")
build.gradle
@CacheableTask                                       (1)
abstract class BundleTask extends NpmTask {

    @Override @Internal                              (2)
    ListProperty<String> getArgs() {
        super.getArgs()
    }

    @InputDirectory
    @SkipWhenEmpty
    @PathSensitive(PathSensitivity.RELATIVE)         (3)
    abstract DirectoryProperty getScripts()

    @InputFiles
    @PathSensitive(PathSensitivity.RELATIVE)         (4)
    abstract ConfigurableFileCollection getConfigFiles()

    @OutputFile
    abstract RegularFileProperty getBundle()

    BundleTask() {
        args.addAll("run", "bundle")
        bundle = projectLayout.buildDirectory.file("bundle.js")
        scripts = projectLayout.projectDirectory.dir("scripts")
        configFiles.from(projectLayout.projectDirectory.file("package.json"))
        configFiles.from(projectLayout.projectDirectory.file("package-lock.json"))
    }
}

tasks.register('bundle', BundleTask)
  • (1) 新增 @CacheableTask 以啟用任務的快取。

  • (2) 覆寫基底類別的屬性的 getter,以將輸入註解變更為 @Internal

  • (3) (4) 宣告路徑敏感度。

使用執行階段 API

如果由於某些原因您無法建立新的自訂任務類別,也可以使用 執行階段 API 來宣告輸入和輸出,使任務可快取。

為了啟用任務的快取,您需要使用 TaskOutputs.cacheIf() 方法。

透過執行階段 API 宣告的效果與上述註解的效果相同。 請注意,您無法透過執行階段 API 覆寫檔案輸入和輸出。 可以透過指定相同的屬性名稱來覆寫輸入屬性。

build.gradle.kts
tasks.register<NpmTask>("bundle") {
    args = listOf("run", "bundle")

    outputs.cacheIf { true }

    inputs.dir(file("scripts"))
        .withPropertyName("scripts")
        .withPathSensitivity(PathSensitivity.RELATIVE)

    inputs.files("package.json", "package-lock.json")
        .withPropertyName("configFiles")
        .withPathSensitivity(PathSensitivity.RELATIVE)

    outputs.file(layout.buildDirectory.file("bundle.js"))
        .withPropertyName("bundle")
}
build.gradle
tasks.register('bundle', NpmTask) {
    args = ['run', 'bundle']

    outputs.cacheIf { true }

    inputs.dir(file("scripts"))
        .withPropertyName("scripts")
        .withPathSensitivity(PathSensitivity.RELATIVE)

    inputs.files("package.json", "package-lock.json")
        .withPropertyName("configFiles")
        .withPathSensitivity(PathSensitivity.RELATIVE)

    outputs.file(layout.buildDirectory.file("bundle.js"))
        .withPropertyName("bundle")
}

配置建置快取

您可以使用 settings.gradle 中的 Settings.buildCache(org.gradle.api.Action) 區塊來配置建置快取。

Gradle 支援可以單獨配置的 localremote 建置快取。 當同時啟用這兩個建置快取時,Gradle 會先嘗試從本機建置快取載入建置輸出,如果找不到建置輸出,則嘗試遠端建置快取。 如果在遠端快取中找到輸出,它們也會儲存在本機快取中,以便下次在本機找到它們。 Gradle 將建置輸出儲存(「推送」)到任何已啟用且 BuildCache.isPush() 設定為 true 的建置快取中。

預設情況下,本機建置快取已啟用推送,而遠端建置快取已停用推送。

本機建置快取已預先配置為 DirectoryBuildCache,並且預設為啟用。 可以透過指定要連接的建置快取類型 (BuildCacheConfiguration.remote(java.lang.Class)) 來配置遠端建置快取。

內建本機建置快取

內建本機建置快取 DirectoryBuildCache 使用目錄來儲存建置快取構件。 預設情況下,此目錄位於 Gradle 使用者首頁中,但其位置是可配置的。

有關配置選項的更多詳細資訊,請參閱 DirectoryBuildCache 的 DSL 文件。 以下是配置範例。

settings.gradle.kts
buildCache {
    local {
        directory = File(rootDir, "build-cache")
    }
}
settings.gradle
buildCache {
    local {
        directory = new File(rootDir, 'build-cache')
    }
}

Gradle 將定期清理本機快取目錄,方法是移除最近未使用過的項目,以節省磁碟空間。 Gradle 執行此清理的頻率以及項目保留時間的長度可透過 init-script 配置,如本節中所示範的。

遠端 HTTP 建置快取

HttpBuildCache 提供透過 HTTP 從遠端快取讀取和寫入的功能。

透過以下配置,本機建置快取將用於儲存建置輸出,而本機和遠端建置快取將用於檢索建置輸出。

settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
    }
}
settings.gradle
buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
    }
}

當嘗試載入項目時,會向 https://example.com:8123/cache/«cache-key» 發出 GET 請求。 回應必須具有 2xx 狀態,並且快取項目作為主體,如果項目不存在,則具有 404 Not Found 狀態。

當嘗試儲存項目時,會向 https://example.com:8123/cache/«cache-key» 發出 PUT 請求。 任何 2xx 回應狀態都將被解釋為成功。 可以傳回 413 Payload Too Large 回應,以指示酬載大於伺服器將接受的大小,這不會被視為錯誤。

指定存取憑證

支援 HTTP 基本驗證,憑證會預先傳送。

settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        credentials {
            username = "build-cache-user"
            password = "some-complicated-password"
        }
    }
}
settings.gradle
buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        credentials {
            username = 'build-cache-user'
            password = 'some-complicated-password'
        }
    }
}

重新導向

將自動追蹤 3xx 重新導向回應。

伺服器在重新導向 PUT 請求時必須小心,因為只有 307308 重新導向回應會透過 PUT 請求追蹤。 所有其他重新導向回應都將透過 GET 請求追蹤,根據 RFC 7231,主體中沒有項目酬載。

網路錯誤處理

在建立 TCP 連線後,在請求傳輸期間失敗的請求將會自動重試。

這可以防止暫時性問題,例如連線中斷、讀取或寫入逾時以及低階網路故障(例如連線重設),導致快取操作失敗並停用建置剩餘部分的遠端快取。

請求將重試最多 3 次。 如果問題仍然存在,快取操作將會失敗,並且遠端快取將會針對建置的剩餘部分停用。

使用 SSL

預設情況下,使用 HTTPS 需要伺服器提供由建置的 Java 執行階段信任的憑證。 如果您的伺服器憑證不受信任,您可以

  1. 更新 Java 執行階段的信任儲存區,以允許信任它

  2. 變更 建置環境 以針對建置執行階段使用替代信任儲存區

  3. 停用對受信任憑證的要求

可以透過將 HttpBuildCache.isAllowUntrustedServer() 設定為 true 來停用信任要求。 啟用此選項存在安全風險,因為它允許任何快取伺服器冒充預期的伺服器。 它僅應作為臨時措施或在嚴格控制的網路環境中使用。

settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        isAllowUntrustedServer = true
    }
}
settings.gradle
buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        allowUntrustedServer = true
    }
}

HTTP expect-continue

可以使用 HTTP Expect-Continue。 這會導致上傳請求分兩個部分進行:首先檢查是否會接受主體,然後在伺服器指示它將接受主體的情況下傳輸主體。

當上傳到例行重新導向或拒絕上傳請求的快取伺服器時,這非常有用,因為它可以避免上傳快取項目只是為了被拒絕(例如,快取項目大於快取將允許的大小)或重新導向。 當伺服器接受請求時,此額外檢查會產生額外延遲,但在請求被拒絕或重新導向時會減少延遲。

並非所有 HTTP 伺服器和 Proxy 都可靠地實作 Expect-Continue。 請務必檢查您的快取伺服器是否支援它,然後再啟用。

若要啟用,請將 HttpBuildCache.isUseExpectContinue() 設定為 true

settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        isUseExpectContinue = true
    }
}
settings.gradle
buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        useExpectContinue = true
    }
}

配置使用案例

遠端建置快取的建議使用案例是,您的持續整合伺服器從乾淨建置填充它,而開發人員僅從中載入。 然後,配置將如下所示。

也可以從 init 腳本 配置建置快取,可以從命令列使用,新增至您的 Gradle 使用者首頁,或成為您的自訂 Gradle 發行版本的一部分。

init.gradle.kts
gradle.settingsEvaluated {
    buildCache {
        // vvv Your custom configuration goes here
        remote<HttpBuildCache> {
            url = uri("https://example.com:8123/cache/")
        }
        // ^^^ Your custom configuration goes here
    }
}
init.gradle
gradle.settingsEvaluated { settings ->
    settings.buildCache {
        // vvv Your custom configuration goes here
        remote(HttpBuildCache) {
            url = 'https://example.com:8123/cache/'
        }
        // ^^^ Your custom configuration goes here
    }
}

建置快取、複合建置和 buildSrc

Gradle 的 複合建置功能 允許將其他完整的 Gradle 建置包含到另一個建置中。 此類包含的建置將繼承頂層建置的建置快取配置,無論包含的建置是否自行定義建置快取配置。

任何包含的建置的建置快取配置都將被有效忽略,而傾向於頂層建置的配置。 這也適用於任何包含的建置的任何 buildSrc 專案。

buildSrc 目錄 被視為 包含的建置,因此它會繼承頂層建置的建置快取配置。

此配置優先順序不適用於透過 pluginManagement 包含的 外掛建置,因為這些建置是在快取配置本身 *之前* 載入的。

如何設定 HTTP 建置快取後端

Gradle 為 建置快取節點 提供 Docker 映像檔,可以與 Develocity 連接以進行集中管理。 快取節點也可以在沒有 Develocity 安裝的情況下使用,但功能受限。

實作您自己的建置快取

使用不同的建置快取後端來儲存建置輸出(內建支援未涵蓋連接到 HTTP 後端),需要實作您自己的邏輯來連接到您的自訂建置快取後端。 為此,可以透過 BuildCacheConfiguration.registerBuildCacheService(java.lang.Class, java.lang.Class) 註冊自訂建置快取類型。

Develocity 包含高效能、易於安裝和操作的共用建置快取後端。