Skip to content

Add experimental Kotlin incremental compilation #1316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

erikkerber
Copy link
Contributor

@erikkerber erikkerber commented May 29, 2025

Note

This is presented as a POC, but nowhere near merge-ready. In its current state, it may compile your apps and you may see significant build time improvements.

This adds an experimental implementation of incremental compilation via the new "Build Tools API" (BTAPI). The BTAPI itself can replace the direct usage of K2JVMCompiler, but is also required to leverage incremental compilation.

Initial Design

In short:

  • Create an alternate KotlinCli wrapper to BazelK2JVMCompiler named BuildToolsApiCompiler. This invokes the BTAPI.
  • Store incremental kotlin artifacts (unsafely) in the execution root (i.e. <output base>/execroot/_kotlin_incremental/)
  • (Temporary) Generate all classpath snapshots in-situ before the build. See TODO item.
  • Add two toolchain flags:
    • build_tools_api to enable building with the new BTAPI (only)
    • incremental_compilation to then add incremental compilation

The rest is fairly trivial usage of the BTAPI, a little of which requires some tinkering and tribal knowledge to know how exactly it is supposed to be used. I did my best to document my knowledge, with brevity.

Performance

The main driver of performance gains are incremental builds that only build a subset of Kotlin files in a target. The incremental data passed in to subsequent builds gives an additional level of work avoidance beyond the action-based caching in Bazel.

I hope to get more comprehensive numbers, but our main test case was a sample incremental build that was ~4x faster in Gradle due to only needing to recompile very small slices of a chain of Kotlin modules. With incremental, Bazel matches what we see with Gradle.

4x isn't a fixed number, it could be greater or smaller depending on how many Kotlin files are in how many dependent targets, and how many files can skip compilation based on the affected classpaths in a build.

image

TODO

  • Track classpath snapshots in Bazel as inputs/outputs
  • Solve for BTAPI dependency w/Kotlin toolchain setup and versioning. Currently it is only distributed via Maven.
  • Settle on a strategy for calculating Kotlin incremental directories. Currently I'm creating isolated directories based on label + metadata passed from CompilationTask. It works, but not idea.
  • Migrate to the new BTAPI via https://github.com/Kotlin/KEEP/blob/build-tools-api/proposals/extensions/build-tools-api.md
  • Tests, everywhere

errStream: java.io.PrintStream,
vararg args: String,
): ExitCode {
System.setProperty("zip.handler.uses.crc.instead.of.timestamp", "true")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to set this, or is this pulled from the K2JVM impl?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulled from BazelK2JVMCompiler. It hits the same internals as the existing implementation on the Kotlin side, and figured if it was still needed there, it may be needed here.

// The incremental directory is roughly analogous to a Gradle transform cache directory. Must be unique per compilation target.
val incrementalDirectory = Paths.get("").toAbsolutePath().parent.resolve("_kotlin_incremental/$label/$incrementalId").toFile()

val kotlinService = CompilationService.loadImplementation(this.javaClass.classLoader!!)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible/safe to keep the CompilationService implementation around?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean simply not loading it every compile task? I'm sure that's safe, yeah.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants