Kotlin Gradle 插件中的编译项与缓存

On this page, you can learn about the following topics:

增量编译

Kotlin Gradle 插件支持支持增量编译。增量编译会跟踪多次构建之间源文件的变更,以便只编译这些变更所影响的文件。

Kotlin/JVM 与 Kotlin/JS 项目均支持增量编译, and is enabled by default.

有几种方法可以禁增量编译:

  • 对于 Kotlin/JVM 设置 kotlin.incremental=false
  • 对于 Kotlin/JS projects 设置 kotlin.incremental.js=false
  • Use -Pkotlin.incremental=false or -Pkotlin.incremental.js=false as a command line parameter.

    该参数必须添加到后续每个子构建。

注意:任何采用禁用增量编译的构建都会使增量缓存失效。首次构建决不会是增量的。

Sometimes problems with incremental compilation become visible several rounds after the failure occurs. Use build reports to track the history of changes and compilations. This can help you to provide reproducible bug reports.

A new approach to incremental compilation

The new approach to incremental compilation is Experimental. It may be dropped or changed at any time. Opt-in is required (see the details below). We encourage you to use it only for evaluation purposes, and we would appreciate your feedback in YouTrack.

The new approach to incremental compilation is available since Kotlin 1.7.0 for the JVM backend in the Gradle build system only. This approach supports changes made inside dependent non-Kotlin modules, includes an improved compilation avoidance, and is compatible with the Gradle build cache.

All of these enhancements decrease the number of non-incremental builds, making the overall compilation time faster. You will receive the most benefit if you use the build cache, or, frequently make changes in non-Kotlin Gradle modules.

To enable this new approach, set the following option in your gradle.properties:

kotlin.incremental.useClasspathSnapshot=true

Learn how the new approach to incremental compilation is implemented under the hood in this blog post.

Gradle 构建缓存支持

The Kotlin plugin uses the Gradle build cache, which stores the build outputs for reuse in future builds.

如需禁用所有 Kotlin 任务的缓存,请将系统属性 kotlin.caching.enabled 设置为 false (运行构建带上参数 -Dkotlin.caching.enabled=false)。

如果使用 kapt,请注意默认情况下不会缓存注解处理任务。不过,可以手动为它们启用缓存

Gradle configuration cache support

Gradle configuration cache support has some constraints:

  • The configuration cache is available in Gradle 6.5 and later as an experimental feature.
    You can check the Gradle releases page to see whether it has been promoted to stable.
  • The feature is supported only by the following Gradle plugins:
    • org.jetbrains.kotlin.jvm
    • org.jetbrains.kotlin.js
    • org.jetbrains.kotlin.android

The Kotlin plugin uses the Gradle configuration cache, which speeds up the build process by reusing the results of the configuration phase.

See the Gradle documentation to learn how to enable the configuration cache. After you enable this feature, the Kotlin Gradle plugin automatically starts using it.

The Kotlin daemon and how to use it with Gradle

The Kotlin daemon:

  • Runs with the Gradle daemon to compile the project.
  • Runs separately from the Gradle daemon when you compile the project with an IntelliJ IDEA built-in build system.

The Kotlin daemon starts at the Gradle execution stage when one of the Kotlin compile tasks starts to compile sources. The Kotlin daemon stops either with the Gradle daemon or after two idle hours with no Kotlin compilation.

The Kotlin daemon uses the same JDK that the Gradle daemon does.

Setting Kotlin daemon's JVM arguments

Each of the following ways to set arguments overrides the ones that came before it:

Gradle daemon arguments inheritance

If nothing is specified, the Kotlin daemon inherits arguments from the Gradle daemon. For example, in the gradle.properties file:

org.gradle.jvmargs=-Xmx1500m -Xms=500m

kotlin.daemon.jvm.options system property

If the Gradle daemon's JVM arguments have the kotlin.daemon.jvm.options system property – use it in the gradle.properties file:

org.gradle.jvmargs=-Dkotlin.daemon.jvm.options=-Xmx1500m,Xms=500m

When passing arguments, follow these rules:

  • Use the minus sign - only before the arguments Xmx, XX:MaxMetaspaceSize, and XX:ReservedCodeCacheSize.
  • Separate arguments with commas (,) without spaces. Arguments that come after a space will be used for the Gradle daemon, not for the Kotlin daemon.

Gradle ignores these properties if all the following conditions are satisfied:

  • Gradle is using JDK 1.9 or higher.
  • The version of Gradle is between 7.0 and 7.1.1 inclusively.
  • Gradle is compiling Kotlin DSL scripts.
  • The Kotlin daemon isn't running.

To overcome this, upgrade Gradle to the version 7.2 (or higher) or use the kotlin.daemon.jvmargs property – see the following section.

kotlin.daemon.jvmargs property

You can add the kotlin.daemon.jvmargs property in the gradle.properties file:

kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m

kotlin extension

You can specify arguments in the kotlin extension:

【Kotlin】

kotlin {
    kotlinDaemonJvmArgs = listOf("-Xmx486m", "-Xms256m", "-XX:+UseParallelGC")
}

【Groovy】

kotlin {
    kotlinDaemonJvmArgs = ["-Xmx486m", "-Xms256m", "-XX:+UseParallelGC"]
}

Specific task definition

You can specify arguments for a specific task:

【Kotlin】

tasks.withType<CompileUsingKotlinDaemon>().configureEach {
    kotlinDaemonJvmArguments.set(listOf("-Xmx486m", "-Xms256m", "-XX:+UseParallelGC"))
}

【Groovy】

tasks.withType(CompileUsingKotlinDaemon::class).configureEach { task ->
    task.kotlinDaemonJvmArguments.set(["-Xmx1g", "-Xms512m"])
}

In this case a new Kotlin daemon instance can start on task execution. Learn more about Kotlin daemon's behavior with JVM arguments.

Kotlin daemon's behavior with JVM arguments

When configuring the Kotlin daemon's JVM arguments, note that:

  • It is expected to have multiple instances of the Kotlin daemon running at the same time when different subprojects or tasks have different sets of JVM arguments.
  • A new Kotlin daemon instance starts only when Gradle runs a related compilation task and existing Kotlin daemons do not have the same set of JVM arguments. Imagine that your project has a lot of subprojects. Most of them require some heap memory for a Kotlin daemon, but one module requires a lot (though it is rarely compiled). In this case, you should provide a different set of JVM arguments for such a module, so a Kotlin daemon with a larger heap size would start only for developers who touch this specific module.

    If you are already running a Kotlin daemon that has enough heap size to handle the compilation request, even if other requested JVM arguments are different, this daemon will be reused instead of starting a new one.

  • If the Xmx argument is not specified, the Kotlin daemon will inherit it from the Gradle daemon.

Defining Kotlin compiler execution strategy

Kotlin compiler execution strategy defines where the Kotlin compiler is executed and if incremental compilation is supported in each case.

There are three compiler execution strategies:

Strategy Where Kotlin compiler is executed Incremental compilation Other characteristics and notes
Daemon Inside its own daemon process Yes The default and fastest strategy. Can be shared between different Gradle daemons and multiple parallel compilations.
In process Inside the Gradle daemon process No May share the heap with the Gradle daemon. The "In process" execution strategy is slower than the "Daemon" execution strategy. Each worker creates a separate Kotlin compiler classloader for each compilation.
Out of process In a separate process for each compilation No The slowest execution strategy. Similar to the "In process", but additionally creates a separate Java process within a Gradle worker for each compilation.

To define a Kotlin compiler execution strategy, you can use one of the following properties:

  • The kotlin.compiler.execution.strategy Gradle property.
  • The compilerExecutionStrategy compile task property.

The task property compilerExecutionStrategy takes priority over the Gradle property kotlin.compiler.execution.strategy.

The available values for the kotlin.compiler.execution.strategy property are:

  1. daemon (default)
  2. in-process
  3. out-of-process

Use the Gradle property kotlin.compiler.execution.strategy in gradle.properties:

kotlin.compiler.execution.strategy=out-of-process

The available values for the compilerExecutionStrategy task property are:

  1. org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy.DAEMON (default)
  2. org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy.IN_PROCESS
  3. org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy.OUT_OF_PROCESS

Use the task property compilerExecutionStrategy in your build scripts:

【Kotlin】

import org.jetbrains.kotlin.gradle.tasks.CompileUsingKotlinDaemon
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy

// ...

tasks.withType<CompileUsingKotlinDaemon>().configureEach {
    compilerExecutionStrategy.set(KotlinCompilerExecutionStrategy.IN_PROCESS)
}

【Groovy】

import org.jetbrains.kotlin.gradle.tasks.CompileUsingKotlinDaemon
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy

// ...

tasks.withType(CompileUsingKotlinDaemon)
    .configureEach {
        compilerExecutionStrategy.set(KotlinCompilerExecutionStrategy.IN_PROCESS)
    }

Kotlin compiler fallback strategy

The Kotlin compiler's fallback strategy is to run a compilation outside a Kotlin daemon if the daemon somehow fails. If the Gradle daemon is on, the compiler uses the "In process" strategy. If the Gradle daemon is off, the compiler uses the "Out of process" strategy.

When this fallback happens, you have the following warning lines in your Gradle's build output:

Failed to compile with Kotlin daemon: java.lang.RuntimeException: Could not connect to Kotlin compile daemon
[exception stacktrace]
Using fallback strategy: Compile without Kotlin daemon
Try ./gradlew --stop if this issue persists.

However, a silent fallback to another strategy can consume a lot of system resources or lead to non-deterministic builds, read more about this in this YouTrack issue. To avoid this, there is a Gradle property kotlin.daemon.useFallbackStrategy, whose default value is true. When the value is false, builds fail on problems with the daemon's startup or communication. Declare this property in gradle.properties:

kotlin.daemon.useFallbackStrategy=false

There is also a useDaemonFallbackStrategy property in Kotlin compile tasks, which takes priority over the Gradle property if you use both.

【Kotlin】

tasks {
    compileKotlin {
        useDaemonFallbackStrategy.set(false)
    }   
}

【Groovy】

tasks.named("compileKotlin").configure {
    useDaemonFallbackStrategy = false
}

If there is insufficient memory to run the compilation, you can see a message about it in the logs.

Build reports

Build reports are Experimental. They may be dropped or changed at any time. Opt-in is required (see details below). Use them only for evaluation purposes. We appreciate your feedback on them in YouTrack.

Build reports for tracking compiler performance are available starting from Kotlin 1.7.0. Reports contain the durations of different compilation phases and reasons why compilation couldn't be incremental.

Use build reports to investigate performance issues when the compilation time is too long or when it differs for the same project.

Kotlin build reports help examine problems more efficiently than Gradle build scans. Lots of engineers use them to investigate build performance, but the unit of granularity in Gradle scans is a single Gradle task.

There are two common cases that analyzing build reports for long-running compilations can help you resolve:

  • The build wasn't incremental. Analyze the reasons and fix underlying problems.
  • The build was incremental but took too much time. Try reorganizing source files — split big files, save separate classes in different files, refactor large classes, declare top-level functions in different files, and so on.

Learn how to read build reports and how build JetBrains uses build reports.

Enabling build reports

To enable build reports, declare where to save the build report output in gradle.properties:

kotlin.build.report.output=file

The following values and their combinations are available for the output:

Option Description
file Saves build reports in a human-readable format to a local file. By default, it's ${project_folder}/build/reports/kotlin-build/${project_name}-timestamp.txt
single_file Saves build reports in a format of an object to a specified local file
build_scan Saves build reports in the custom values section of the build scan. Note that the Gradle Enterprise plugin limits the number of custom values and their length. In big projects, some values could be lost
http Posts build reports using HTTP(S). The POST method sends metrics in JSON format. You can see the current version of the sent data in the Kotlin repository. You can find samples of HTTP endpoints in this blog post

Here's the full list of available options for kotlin.build.report:

# Required outputs. Any combination is allowed
kotlin.build.report.output=file,single_file,http,build_scan

# Mandatory if single_file output is used. Where to put reports 
# Use instead of the deprecated `kotlin.internal.single.build.metrics.file` property
kotlin.build.report.single_file=some_filename

# Optional. Output directory for file-based reports. Default: build/reports/kotlin-build/
kotlin.build.report.file.output_dir=kotlin-reports

# Mandatory if HTTP output is used. Where to post HTTP(S)-based reports
kotlin.build.report.http.url=http://127.0.0.1:8080

# Optional. User and password if the HTTP endpoint requires authentication
kotlin.build.report.http.user=someUser
kotlin.build.report.http.password=somePassword

# Optional. Label for marking your build report (for example, debug parameters)
kotlin.build.report.label=some_label

Limit of custom values

To collect build scans' statistics, Kotlin build reports use Gradle's custom values. Different Gradle plugins and you can also write data to custom values. The number of custom values has a limit. See the current maximum custom value count in the Build scan plugin docs. If you have a big project, a number of such custom values may be quite big. If this number exceeds the limit, you can see the following message in the logs:

Maximum number of custom values (1,000) exceeded

To reduce the number of custom values the Kotlin plugin produces, you can use the following property in gradle.properties:

kotlin.build.report.build_scan.custom_values_limit=500

Switching off collecting project and system properties

HTTP build statistic logs can contain some project and system properties. These properties can change builds' behavior, so it's useful to log them in build statistics. These properties can store sensitive data, for example, passwords or a project's full path. You can turn on this behavior by adding the kotlin.build.report.http.verbose_environment property to your gradle.properties.

JetBrains doesn't collect these statistics. You choose a place where to store your reports.

下一步做什么?

Learn more about: