想要學習頂尖工程團隊用來保持組建快速且高效的訣竅和技巧嗎?在此註冊參加我們的組建快取訓練。

概觀

Gradle 組建快取是一種快取機制,旨在透過重複使用其他組建產生的輸出,來節省時間。組建快取透過儲存(本地或遠端)組建輸出,並允許組建從快取擷取這些輸出(當確定輸入未變更時),來避免重新產生這些輸出的昂貴工作。

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

除了任務之外,人工產出轉換 也可以利用建置快取,並以類似任務輸出快取的方式重新使用其輸出。

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

啟用建置快取

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

在命令列上執行 --build-cache

Gradle 只會針對此建置使用建置快取。

gradle.properties 中輸入 org.gradle.caching=true

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 是類似複製的任務,由於執行它們通常較快,因此不可快取。

可快取任務

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

下列輸入會以與最新檢查相同的方式,促成任務的組建快取金鑰

  • 任務類型及其類別路徑

  • 輸出屬性的名稱

  • 名為「自訂任務類型」一節中所述,屬性的名稱和值,註解為

  • 透過TaskInputs,DSL 新增的屬性名稱和值

  • Gradle 發行版、buildSrc 和外掛的 classpath

  • 當影響任務執行時,建置指令碼的內容

任務類型需要使用 @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 開發人員,您會知道綑綁可能會很長,而且值得快取。為了達成此目的,我們需要使用 @CacheableTask 註解告訴 Gradle 它允許快取該任務的輸出。

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

請注意,可以透過覆寫基本類別的 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 使用者主目錄中,但其位置是可以設定的。

Gradle 會定期清除本機快取目錄,移除最近未使用的項目以節省磁碟空間。Gradle 執行此清除作業的頻率是可以設定的,如下面的範例所示。請注意,快取項目會清除,而不論它們是由哪個專案產生的。如果不同的專案設定此清除作業在不同的時間段執行,最短的時間段會清除所有專案的快取項目。因此,建議在 初始化指令碼 中針對此設定進行全域設定。設定使用案例 區段有一個範例,說明如何將快取設定放入初始化指令碼中。

如需設定選項的更多詳細資料,請參閱 DirectoryBuildCache 的 DSL 文件。以下是設定範例。

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

遠端 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 伺服器和代理程式都能可靠地實作 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
    }
}

設定使用案例

遠端建置快取建議的使用案例是讓您的持續整合伺服器從乾淨的建置中填入資料,而開發人員只從中載入。然後,設定會如下所示。

也可以從 初始化指令碼 設定建置快取,它可以用於命令列,新增至您的 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 包含一個效能高、易於安裝和操作的共用建置快取後端。