Skip to content

Commit c438c2b

Browse files
authored
KTOR-7667: Enable Gradle configuration cache (#4741)
* Fix compatibility with configuration cache * Add a workaround for KT-72933 * Add a workaround for KT-76147
1 parent 8a8b186 commit c438c2b

9 files changed

+93
-6
lines changed

build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts

+6-3
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,16 @@ if (targets.hasJsOrWasmJs) {
5858
@Suppress("UnstableApiUsage")
5959
if (targets.hasNative) {
6060
tasks.maybeNamed("linkDebugTestLinuxX64") {
61-
onlyIf("run only on Linux") { ktorBuild.os.get().isLinux }
61+
val os = ktorBuild.os.get()
62+
onlyIf("run only on Linux") { os.isLinux }
6263
}
6364
tasks.maybeNamed("linkDebugTestLinuxArm64") {
64-
onlyIf("run only on Linux") { ktorBuild.os.get().isLinux }
65+
val os = ktorBuild.os.get()
66+
onlyIf("run only on Linux") { os.isLinux }
6567
}
6668
tasks.maybeNamed("linkDebugTestMingwX64") {
67-
onlyIf("run only on Windows") { ktorBuild.os.get().isWindows }
69+
val os = ktorBuild.os.get()
70+
onlyIf("run only on Windows") { os.isWindows }
6871
}
6972
}
7073

build-logic/src/main/kotlin/ktorbuild.publish.gradle.kts

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ configureSigning()
5757

5858
plugins.withId("ktorbuild.kmp") {
5959
tasks.withType<AbstractPublishToMaven>().configureEach {
60-
onlyIf { isAvailableForPublication(ktorBuild.os.get()) }
60+
val os = ktorBuild.os.get()
61+
onlyIf { isAvailableForPublication(os) }
6162
}
6263

6364
registerTargetsPublishTasks(ktorBuild.targets)

build-logic/src/main/kotlin/ktorbuild/CInterop.kt

+7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import ktorbuild.targets.KtorTargets
88
import org.gradle.kotlin.dsl.assign
99
import org.gradle.kotlin.dsl.getValue
1010
import org.gradle.kotlin.dsl.provideDelegate
11+
import org.gradle.kotlin.dsl.withType
1112
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
1213
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultCInteropSettings
1314
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
15+
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
1416

1517
/**
1618
* Creates a CInterop configuration for all Native targets using the given [sourceSet]
@@ -62,4 +64,9 @@ fun KotlinMultiplatformExtension.createCInterop(
6264
configure(targetName)
6365
}
6466
}
67+
68+
// Disable configuration cache for compile[target]MainKotlinMetadata tasks
69+
project.tasks.withType<KotlinNativeCompile>()
70+
.named { it.endsWith("MainKotlinMetadata") }
71+
.configureEach { notCompatibleWithConfigurationCache("Workaround for KT-76147") }
6572
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
import sun.misc.Unsafe
6+
import java.lang.reflect.Field
7+
8+
@Suppress("UNCHECKED_CAST")
9+
internal object ConfigurationCacheWorkarounds {
10+
private val ignoreBeanFieldsField = Class.forName("org.gradle.internal.serialize.beans.services.Workarounds")
11+
.getDeclaredField("ignoredBeanFields")
12+
.apply { isAccessible = true }
13+
14+
private val unsafe = runCatching {
15+
Unsafe::class.java.getDeclaredField("theUnsafe")
16+
.apply { isAccessible = true }
17+
.get(null) as Unsafe
18+
}.getOrNull()
19+
20+
private var ignoreBeanFields = ignoreBeanFieldsField.get(null) as Array<Pair<String, String>>
21+
set(value) {
22+
val isSet = runCatching { unsafe?.setFinalStatic(ignoreBeanFieldsField, value) }.getOrNull() != null
23+
if (isSet) {
24+
field = value
25+
} else {
26+
// If we can't set a new array to the field, fallback to replacing array's content with new values
27+
for (i in 0..minOf(field.lastIndex, value.lastIndex)) field[i] = value[i]
28+
}
29+
}
30+
31+
/** Registers fields that should be excluded from configuration cache. */
32+
fun addIgnoredBeanFields(vararg fields: Pair<String, String>) {
33+
ignoreBeanFields = fields as Array<Pair<String, String>> + ignoreBeanFields
34+
}
35+
}
36+
37+
@Suppress("DEPRECATION")
38+
private fun Unsafe.setFinalStatic(field: Field, value: Any) {
39+
val fieldBase = staticFieldBase(field)
40+
val fieldOffset = staticFieldOffset(field)
41+
42+
putObject(fieldBase, fieldOffset, value)
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
import org.gradle.kotlin.dsl.support.serviceOf
6+
7+
val features = serviceOf<BuildFeatures>()
8+
9+
if (features.configurationCache.requested.get()) {
10+
// KT-72933: Storing these fields leads to OOM
11+
ConfigurationCacheWorkarounds.addIgnoredBeanFields(
12+
"transformationParameters" to "org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyTransformationTask",
13+
"parameters" to "org.jetbrains.kotlin.gradle.targets.native.internal.CInteropMetadataDependencyTransformationTask",
14+
)
15+
16+
// Mark these tasks as incompatible with configuration cache as we partially exclude their state from CC
17+
gradle.beforeProject {
18+
tasks.matching { it::class.java.simpleName.contains("MetadataDependencyTransformationTask") }
19+
.configureEach { notCompatibleWithConfigurationCache("Workaround for KT-72933") }
20+
}
21+
22+
println("Configuration Cache: Workaround for KT-72933 was applied")
23+
}

build-settings-logic/src/main/kotlin/ktorbuild.develocity.settings.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ develocity {
1818
// Should be in sync with settings.gradle.kts
1919
server = "https://ge.jetbrains.com"
2020

21+
// Copy the value to the local variable for compatibility with configuration cache
22+
val isCIRun = isCIRun
23+
2124
buildScan {
2225
uploadInBackground = !isCIRun
2326

gradle.properties

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ doctor.enableTaskMonitoring=false
4343
org.gradle.daemon=true
4444
org.gradle.caching=true
4545
org.gradle.parallel=true
46+
org.gradle.configuration-cache=true
47+
org.gradle.configuration-cache.parallel=true
4648
org.gradle.kotlin.dsl.allWarningsAsErrors=true
4749

4850
# kotlin
@@ -54,6 +56,9 @@ kotlin.mpp.applyDefaultHierarchyTemplate=false
5456
kotlin.apple.xcodeCompatibility.nowarn=true
5557
kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning
5658
kotlin.daemon.useFallbackStrategy=false
59+
# Enable new project model to be prepared for enabling isolated projects
60+
# TODO: Remove when we enable isolated projects in Gradle
61+
kotlin.kmp.isolated-projects.support=enable
5762

5863
# dokka
5964
# workaround for resolving platform dependencies, see https://github.com/Kotlin/dokka/issues/3153

ktor-test-server/src/main/kotlin/test-server.gradle.kts

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
import test.server.TestServerService
66

7-
val testServerService = TestServerService.registerIfAbsent(project)
8-
97
tasks.withType<AbstractTestTask>().configureEach {
8+
val testServerService = TestServerService.registerIfAbsent(project)
109
usesService(testServerService)
1110
// Trigger server start if it is not started yet
1211
doFirst("start test server") { testServerService.get() }

settings.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
6+
57
pluginManagement {
68
includeBuild("build-settings-logic")
79
}
@@ -10,6 +12,7 @@ plugins {
1012
id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
1113
id("conventions-dependency-resolution-management")
1214
id("ktorbuild.develocity")
15+
id("ktorbuild.configuration-cache")
1316
}
1417

1518
rootProject.name = "ktor"

0 commit comments

Comments
 (0)