💡
是否想了解顶级工程团队用来保持构建快速和高性能的提示和技巧? 在此处注册以获取我们的构建缓存培训.
此处描述的构建缓存功能与Android插件构建缓存不同 .

Overview

Gradle 构建缓存是一种缓存机制,旨在通过重用其他构建生成的输出来节省时间. 构建缓存通过以下方式工作:存储(本地或远程)构建输出,并允许构建在确定输入未更改时从缓存中获取这些输出,从而避免了重新生成它们的昂贵工作.

使用构建缓存的第一个功能是任务输出缓存 . 本质上,任务输出缓存利用了与Gradle用于避免以前的本地版本已经产生一组任务输出的最新检查相同的情报. 但是,任务输出缓存不仅限于同一个工作空间中的先前版本,还使Gradle可以重用本地计算机上任何位置的任何早期版本的任务输出. 当使用共享的构建缓存进行任务输出缓存时,这甚至可以在开发人员机器和构建代理之间使用.

Apart from tasks, artifact transforms can also leverage the build cache and re-use their outputs similarly to task output caching.

💡
有关学习如何使用构建缓存的动手方法,请尝试使用"构建缓存"指南. 它涵盖了可以改进缓存的不同方案,并详细讨论了为构建启用缓存时需要注意的不同警告.

Enable the Build Cache

默认情况下,不启用构建缓存. 您可以通过以下两种方式启用构建缓存:

Run with --build-cache on the command-line

Gradle仅将构建缓存用于此构建.

Put org.gradle.caching=true in your gradle.properties

除非使用--no-build-cache明确禁用,否则Gradle将尝试对所有内部版本重复使用先前内部版本的输出.

When the build cache is enabled, it will store build outputs in the Gradle user home. For configuring this directory or different kinds of build caches see Configure the Build Cache.

Task Output Caching

除了最新检查中描述的增量构建之外,Gradle还可以通过将输入与任务匹配来重用任务以前执行的输出,从而节省时间. 任务输出可以通过构建缓存在一台计算机上的构建之间,甚至在不同计算机上运行的构建之间重复使用.

我们关注的用例是用户拥有整个组织范围内的远程构建缓存,该缓存由连续的集成构建定期填充. 开发人员和其他持续集成代理应从远程构建缓存中加载缓存条目. 我们预计将不允许开发人员填充远程构建缓存,并且在运行clean任务后,所有连续集成的构建都将填充构建缓存.

为了使您的构建能够很好地使用任务输出缓存,它必须与增量构建功能一起正常工作. 例如,当连续两次运行构建时,所有带有输出的任务都应为UP-TO-DATE . 在不满足此先决条件的情况下启用任务输出缓存时,您不能期望更快的构建或正确的构建.

启用构建缓存时,将自动启用任务输出缓存,请参阅启用构建缓存 .

What does it look like

让我们从使用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的任务,由于无法更快地执行它们,因此无法缓存.

Cacheable tasks

由于任务描述了其所有输入和输出,因此Gradle可以计算生成缓存密钥 ,该密钥根据其输入唯一定义任务的输出. 该构建缓存键用于从构建缓存请求先前的输出或将新的输出存储在构建缓存中. 如果先前的构建输出已经被其他人(例如您的持续集成服务器或其他开发人员)存储在缓存中,则可以避免在本地执行大多数任务.

下列输入以与最新检查相同的方式为任务的构建缓存键提供帮助:

  • 任务类型及其类路径

  • 输出属性的名称

  • "自定义任务类型"一节中所述,注释的属性名称和值

  • DSL通过TaskInputs添加的属性的名称和值

  • Gradle发行版,buildSrc和插件的类路径

  • 当构建脚本影响任务执行时的内容

任务类型需要使用@CacheableTask批注选择加入任务输出缓存. 请注意, @ CacheableTask不是子类继承的. 自定义任务类型默认情况下不可缓存.

Built-in cacheable tasks

当前,以下内置Gradle任务是可缓存的:

当前所有其他内置任务都不可缓存.

诸如CopyJar之类的某些任务通常没有意义使其可缓存,因为Gradle只是将文件从一个位置复制到另一位置. 使不产生输出或没有任务动作的可缓存任务也没有意义.

Third party plugins

有一些第三方插件可以很好地与构建缓存配合使用. 最突出的示例是Android插件3.1+Kotlin插件1.2.21+ . 对于其他第三方插件,请查看其文档以了解它们是否支持构建缓存.

Declaring task inputs and outputs

具有可缓存任务的输入和输出的完整图片非常重要,这样一个构建的结果就可以安全地在其他地方重复使用.

缺少任务输入会导致不正确的缓存命中,其中,由于两次执行都使用相同的缓存键,因此不同的结果被视为相同. 如果Gradle不能完全捕获给定任务的所有输出,则缺少任务输出会导致构建失败. 错误声明的任务输入会导致高速缓存未命中,尤其是在包含易失性数据或绝对路径时. (有关应声明为输入和输出的信息,请参见"任务输入和输出"一节.)

任务路径不是构建缓存键的输入. 这意味着具有不同任务路径的任务可以重用彼此的输出,只要Gradle确定执行它们会产生相同的结果即可.

为了确保正确声明了输入和输出,请使用集成测试(例如,使用TestKit)检查任务为相同的输入产生相同的输出并捕获该任务的所有输出文件. 我们建议添加测试以确保任务输入可重定位,即可以将任务从缓存加载到其他构建目录中(请参阅@PathSensitive ).

为了处理任务的易失性输入,请考虑配置输入标准化 .

Enable caching of non-cacheable tasks

如我们所见,如果内置任务或插件提供的任务的类使用Cacheable注释进行了注释,则它们是可Cacheable . 但是,如果您想使可缓存任务成为其不可缓存的类,该怎么办? 让我们举一个具体的例子:您的构建脚本使用通用的NpmTask任务通过委派给NPM(并运行npm run bundle )来创建JavaScript捆绑npm run bundle . 此过程类似于复杂的编译任务,但NpmTask太通用而无法默认缓存:它仅接受参数并使用这些参数运行npm.

这个任务的输入和输出很容易弄清楚. 输入是包含JavaScript文件和NPM配置文件的目录. 输出是此任务生成的捆绑文件.

Using annotations

我们创建NpmTask的子类,并使用批注声明输入和输出 .

如果可能,最好使用委托而不是创建子类. 内置的JavaExecExecCopySync任务就是这种情况,这些任务在Project具有执行实际工作的方法.

如果您是一名现代JavaScript开发人员,那么您就会知道捆绑可能会很长,值得缓存. 为此,我们需要使用@CacheableTask批注告诉Gradle它允许缓存该任务的输出.

这足以使任务可缓存在您自己的计算机上. 但是,默认情况下,输入文件由其绝对路径标识. 因此,如果需要在使用不同路径的多个开发人员或计算机之间共享缓存,则将无法正常工作. 因此,我们还需要设置路径灵敏度 . 在这种情况下,可以使用输入文件的相对路径来标识它们.

请注意,可以通过覆盖基类的getter并对该方法进行注释来覆盖基类的属性注释.

例子1.定制的可缓存BundleTask
build.gradle
@CacheableTask                                       // (1)
class BundleTask extends NpmTask {

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

    @InputDirectory
    @SkipWhenEmpty
    @PathSensitive(PathSensitivity.RELATIVE)         // (3)
    final DirectoryProperty scripts = project.objects.directoryProperty()

    @InputFiles
    @PathSensitive(PathSensitivity.RELATIVE)         // (4)
    final ConfigurableFileCollection configFiles = project.files()

    @OutputFile
    final RegularFileProperty bundle = project.objects.fileProperty()

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

task bundle(type: BundleTask)
build.gradle.kts
@CacheableTask                                       // (1)
open class BundleTask : NpmTask() {

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


    @get:InputDirectory
    @get:SkipWhenEmpty
    @get:PathSensitive(PathSensitivity.RELATIVE)     // (3)
    val scripts: DirectoryProperty = project.objects.directoryProperty()

    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)     // (4)
    val configFiles: ConfigurableFileCollection = project.files()

    @get:OutputFile
    val bundle: RegularFileProperty = project.objects.fileProperty()

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

tasks.register<BundleTask>("bundle")
  • (1)添加@Cacheable以启用任务缓存.

  • (2)重写基类的属性的getter,以将输入注释更改为@Internal .

  • (3)(4)声明路径灵敏度.

Using the runtime API

If for some reason you cannot create a new custom task class, it is also possible to make a task cacheable using the runtime API to declare the inputs and outputs.

要为任务启用缓存,您需要使用TaskOutputs.cacheIf()方法.

通过运行时API进行的声明与上述注释具有相同的效果. 请注意,您无法通过运行时API覆盖文件输入和输出. 通过指定相同的属性名称,可以覆盖输入属性.

示例2.使包任务可缓存
build.gradle
task bundle(type: 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("$buildDir/bundle.js")
        .withPropertyName("bundle")
}
build.gradle.kts
tasks.register<NpmTask>("bundle") {
    args.set(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("$buildDir/bundle.js")
        .withPropertyName("bundle")
}

Configure the Build Cache

您可以通过配置构建缓存Settings.buildCache(org.gradle.api.Action)settings.gradle .

Gradle支持可以分别配置的localremote构建缓存. 当两个构建缓存都启用时,Gradle首先尝试从本地构建缓存加载构建输出,然后如果找不到构建输出,则尝试远程构建缓存. 如果在远程缓存中找到了输出,则它们也将存储在本地缓存中,因此下次将在本地找到它们. Gradle存储("推送")在任何已启用且BuildCache.isPush()设置为true构建缓存中的构建输出.

默认情况下,本地构建缓存已启用推送,而远程构建缓存已禁用推送.

本地构建缓存已预先配置为DirectoryBuildCache并默认启用. 可以通过指定要连接到的构建缓存的类型( BuildCacheConfiguration.remote(java.lang.Class) )来配置远程构建缓存.

Built-in local build cache

内置的本地构建缓存DirectoryBuildCache使用一个目录来存储构建缓存工件. 默认情况下,该目录位于Gradle用户主目录中,但其位置是可配置的.

Gradle将通过删除最近尚未使用的条目来节省磁盘空间,从而定期清理本地缓存目录.

有关配置选项的更多详细信息,请参考DirectoryBuildCache的DSL文档. 这是配置示例.

例子3.配置本地缓存
settings.gradle
buildCache {
    local {
        directory = new File(rootDir, 'build-cache')
        removeUnusedEntriesAfterDays = 30
    }
}
settings.gradle.kts
buildCache {
    local {
        directory = File(rootDir, "build-cache")
        removeUnusedEntriesAfterDays = 30
    }
}

Remote HTTP build cache

Gradle具有内置支持,可通过HTTP连接到远程构建缓存后端. 有关协议外观的更多详细信息,请参见HttpBuildCache . 请注意,通过使用以下配置,本地构建缓存将用于存储构建输出,而本地和远程构建缓存将用于检索构建输出.

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

您可以配置HttpBuildCache用于访问生成缓存服务器的凭据,如以下示例所示.

例子5.配置远程HTTP缓存
settings.gradle
buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        credentials {
            username = 'build-cache-user'
            password = 'some-complicated-password'
        }
    }
}
settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        credentials {
            username = "build-cache-user"
            password = "some-complicated-password"
        }
    }
}

当您尝试将生成缓存后端与HTTPS URL结合使用时,可能会遇到不受信任的SSL证书问题. 理想的解决方案是为某人添加有效的SSL证书到构建缓存后端,但是我们认识到您可能无法做到这一点. 在这种情况下,请将HttpBuildCache.isAllowUntrustedServer()设置为true .

这是一个方便的解决方法,但是您不应该将其用作长期解决方案.

例子6.允许不受信任的缓存服务器
settings.gradle
buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        allowUntrustedServer = true
    }
}
settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        isAllowUntrustedServer = true
    }
}

Configuration use cases

远程构建缓存的推荐用例是,持续集成服务器从干净的构建中填充它,而开发人员仅从其加载. 然后,配置将如下所示.

示例7. CI推送用例的推荐设置
settings.gradle
boolean isCiServer = System.getenv().containsKey("CI")

buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        push = isCiServer
    }
}
settings.gradle.kts
val isCiServer = System.getenv().containsKey("CI")

buildCache {
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        isPush = isCiServer
    }
}

如果使用buildSrc目录,则应确保它使用与主构建相同的构建缓存配置. 可以通过将相同的脚本应用于buildSrc/settings.gradlesettings.gradle来实现,如以下示例所示.

示例8. buildSrc和主版本的一致设置
settings.gradle
apply from: new File(settingsDir, 'gradle/buildCacheSettings.gradle')
gradle/buildCacheSettings.gradle
boolean isCiServer = System.getenv().containsKey("CI")

buildCache {
    local {
        enabled = !isCiServer
    }
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        push = isCiServer
    }
}
buildSrc/settings.gradle
apply from: new File(settingsDir, '../gradle/buildCacheSettings.gradle')
settings.gradle.kts
apply(from = File(settingsDir, "gradle/buildCacheSettings.gradle.kts"))
gradle/buildCacheSettings.gradle.kts
val isCiServer = System.getenv().containsKey("CI")

buildCache {
    local {
        isEnabled = !isCiServer
    }
    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")
        isPush = isCiServer
    }
}
buildSrc/settings.gradle.kts
apply(from = File(settingsDir, "../gradle/buildCacheSettings.gradle.kts"))

也可以从init脚本配置构建缓存,该脚本可以在命令行中使用,可以添加到Gradle用户主目录中,也可以作为自定义Gradle发行版的一部分.

例子9.初始化脚本来配置构建缓存
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
    }
}
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
    }
}

Build cache and composite builds

Gradle的复合构建功能允许将其他完整的Gradle构建包含到另一个构建中. 无论所包含的构建是否自行定义构建缓存配置,此类包含的构建都会从顶级构建中继承构建缓存配置.

对于任何包含的构建,当前存在的构建缓存配置都将被有效忽略,而有利于顶层构建的配置. 这也适用于任何包含的内部版本的所有buildSrc项目.

How to set up an HTTP build cache backend

Gradle为构建缓存节点提供Docker映像,该映像可与Gradle Enterprise连接以进行集中管理. 也可以不使用功能受限的Gradle Enterprise安装缓存节点.

Implement your own Build Cache

使用不同的构建缓存后端存储构建输出(连接到HTTP后端的内置支持未涵盖在内)要求实现自己的逻辑以连接到自定义构建缓存后端. 为此,可以通过BuildCacheConfiguration.registerBuildCacheService(java.lang.Class,java.lang.Class)注册自定义构建缓存类型.

Gradle Enterprise包含一个高性能,易于安装和操作的共享构建缓存后端.