diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt
index caeac692ef0..3d9c326490f 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt
@@ -13,7 +13,6 @@ import org.jetbrains.compose.internal.mppExtOrNull
 import org.jetbrains.compose.internal.webExt
 import org.jetbrains.kotlin.gradle.plugin.*
 import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
-import java.lang.ClassCastException
 import javax.inject.Inject
 
 @Suppress("UnstableApiUsage")
@@ -48,24 +47,7 @@ class ComposeCompilerKotlinSupportPlugin @Inject constructor(
             return !groupId.startsWith("org.jetbrains.compose.compiler")
         }
 
-        val service = ComposeMultiplatformBuildService.provider(target)
-        buildEventsListenerRegistry.onTaskCompletion(service)
-
-        val providedService = try {
-            service.get()
-        } catch (e: ClassCastException) {
-            // Compose Gradle plugin was probably loaded more than once
-            // See https://github.com/JetBrains/compose-multiplatform/issues/3459
-
-            throw IllegalStateException(
-                "Failed to get ComposeMultiplatformBuildService instance." +
-                        " Compose Gradle plugin was probably loaded more than once." +
-                        " Consider declaring it in the root build.gradle.kts",
-                e
-            )
-        }
-
-        providedService.parameters.unsupportedCompilerPlugins.add(
+        ComposeMultiplatformBuildService.getInstance(target).unsupportedCompilerPlugins.add(
             target.provider {
                 composeCompilerArtifactProvider.compilerArtifact.takeIf {
                     target.hasNonJvmTargets() && it.isNonJBComposeCompiler()
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt
index 5e117da92ee..e8da1a73c4a 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt
@@ -6,27 +6,32 @@ import org.gradle.api.provider.Provider
 import org.gradle.api.provider.SetProperty
 import org.gradle.api.services.BuildService
 import org.gradle.api.services.BuildServiceParameters
-import org.gradle.api.services.BuildServiceRegistry
 import org.gradle.tooling.events.FinishEvent
 import org.gradle.tooling.events.OperationCompletionListener
+import org.jetbrains.compose.internal.utils.BuildEventsListenerRegistryProvider
 import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
 
 // The service implements OperationCompletionListener just so Gradle would use the service
 // even if the service is not used by any task or transformation
-abstract class ComposeMultiplatformBuildService : BuildService<ComposeMultiplatformBuildService.Parameters>,
+abstract class ComposeMultiplatformBuildService : BuildService<BuildServiceParameters.None>,
     OperationCompletionListener, AutoCloseable {
-    interface Parameters : BuildServiceParameters {
-        val unsupportedCompilerPlugins: SetProperty<Provider<SubpluginArtifact?>>
-    }
 
     private val log = Logging.getLogger(this.javaClass)
 
+    internal abstract val unsupportedCompilerPlugins: SetProperty<Provider<SubpluginArtifact?>>
+    internal abstract val delayedWarnings: SetProperty<String>
+
+    fun warnOnceAfterBuild(message: String) {
+        delayedWarnings.add(message)
+    }
+
     override fun close() {
         notifyAboutUnsupportedCompilerPlugin()
+        logDelayedWarnings()
     }
 
     private fun notifyAboutUnsupportedCompilerPlugin() {
-        val unsupportedCompilerPlugin = parameters.unsupportedCompilerPlugins.orNull
+        val unsupportedCompilerPlugin = unsupportedCompilerPlugins.orNull
             ?.firstOrNull()
             ?.orNull
 
@@ -35,28 +40,55 @@ abstract class ComposeMultiplatformBuildService : BuildService<ComposeMultiplatf
         }
     }
 
+    private fun logDelayedWarnings() {
+        for (warning in delayedWarnings.get()) {
+            log.warn(warning)
+        }
+    }
+
     override fun onFinish(event: FinishEvent) {}
 
     companion object {
-        fun configure(project: Project, fn: Parameters.() -> Unit): Provider<ComposeMultiplatformBuildService> =
-            project.gradle.sharedServices.registerOrConfigure<Parameters, ComposeMultiplatformBuildService> {
-                fn()
+        private val COMPOSE_SERVICE_FQ_NAME = ComposeMultiplatformBuildService::class.java.canonicalName
+
+        private fun findExistingComposeService(project: Project): ComposeMultiplatformBuildService? {
+            val registration = project.gradle.sharedServices.registrations.findByName(COMPOSE_SERVICE_FQ_NAME)
+            val service = registration?.service?.orNull
+            if (service != null) {
+                if (service !is ComposeMultiplatformBuildService) {
+                    // Compose Gradle plugin was probably loaded more than once
+                    // See https://github.com/JetBrains/compose-multiplatform/issues/3459
+                    if (service.javaClass.canonicalName == ComposeMultiplatformBuildService::class.java.canonicalName) {
+                        val rootScript = project.rootProject.buildFile
+                        error("""
+                            Compose Multiplatform Gradle plugin has been loaded in multiple classloaders.
+                            To avoid classloading issues, declare Compose Gradle Plugin in root build file $rootScript.
+                        """.trimIndent())
+                    } else {
+                        error("Shared build service '$COMPOSE_SERVICE_FQ_NAME' has unexpected type: ${service.javaClass.canonicalName}")
+                    }
+                }
+                return service
             }
 
-        fun provider(project: Project): Provider<ComposeMultiplatformBuildService> = configure(project) {}
+            return null
+        }
+
+        @Suppress("UnstableApiUsage")
+        fun init(project: Project) {
+            val existingService = findExistingComposeService(project)
+            if (existingService != null) {
+                return
+            }
+
+            val newService = project.gradle.sharedServices.registerIfAbsent(COMPOSE_SERVICE_FQ_NAME, ComposeMultiplatformBuildService::class.java) {
+            }
+            // workaround to instanciate a  service even if it not binded to a task
+            BuildEventsListenerRegistryProvider.getInstance(project).onTaskCompletion(newService)
+        }
+
+        fun getInstance(project: Project): ComposeMultiplatformBuildService =
+            findExistingComposeService(project) ?: error("ComposeMultiplatformBuildService was not initialized!")
     }
 }
 
-inline fun <reified P : BuildServiceParameters, reified S : BuildService<P>> BuildServiceRegistry.registerOrConfigure(
-    crossinline fn: P.() -> Unit
-): Provider<S> {
-    val serviceClass = S::class.java
-    val serviceFqName = serviceClass.canonicalName
-    val existingService = registrations.findByName(serviceFqName)
-        ?.apply { (parameters as? P)?.fn() }
-        ?.service
-    return (existingService as? Provider<S>)
-        ?: registerIfAbsent(serviceFqName, serviceClass) {
-            it.parameters.fn()
-        }
-}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt
index 041a68bb4a6..d7e473e28ef 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt
@@ -21,6 +21,7 @@ import org.jetbrains.compose.desktop.preview.internal.initializePreview
 import org.jetbrains.compose.experimental.dsl.ExperimentalExtension
 import org.jetbrains.compose.experimental.internal.configureExperimentalTargetsFlagsCheck
 import org.jetbrains.compose.experimental.internal.configureExperimental
+import org.jetbrains.compose.experimental.internal.configureNativeCompilerCaching
 import org.jetbrains.compose.experimental.uikit.internal.resources.configureSyncTask
 import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
 import org.jetbrains.compose.internal.mppExt
@@ -35,8 +36,10 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 
 internal val composeVersion get() = ComposeBuildConfig.composeVersion
 
-class ComposePlugin : Plugin<Project> {
+abstract class ComposePlugin : Plugin<Project> {
     override fun apply(project: Project) {
+        ComposeMultiplatformBuildService.init(project)
+
         val composeExtension = project.extensions.create("compose", ComposeExtension::class.java, project)
         val desktopExtension = composeExtension.extensions.create("desktop", DesktopExtension::class.java)
         val androidExtension = composeExtension.extensions.create("android", AndroidExtension::class.java)
@@ -52,6 +55,7 @@ class ComposePlugin : Plugin<Project> {
         composeExtension.extensions.create("web", WebExtension::class.java)
 
         project.plugins.apply(ComposeCompilerKotlinSupportPlugin::class.java)
+        project.configureNativeCompilerCaching()
 
         project.afterEvaluate {
             configureDesktop(project, desktopExtension)
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ComposeProjectProperties.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ComposeProjectProperties.kt
index ab1011bc808..08123a41254 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ComposeProjectProperties.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ComposeProjectProperties.kt
@@ -7,7 +7,7 @@ package org.jetbrains.compose.desktop.application.internal
 
 import org.gradle.api.provider.Provider
 import org.gradle.api.provider.ProviderFactory
-import org.jetbrains.compose.internal.utils.findProperty
+import org.jetbrains.compose.internal.utils.valueOrNull
 import org.jetbrains.compose.internal.utils.toBooleanProvider
 
 internal object ComposeProperties {
@@ -23,32 +23,32 @@ internal object ComposeProperties {
     internal const val CHECK_JDK_VENDOR = "compose.desktop.packaging.checkJdkVendor"
 
     fun isVerbose(providers: ProviderFactory): Provider<Boolean> =
-        providers.findProperty(VERBOSE).toBooleanProvider(false)
+        providers.valueOrNull(VERBOSE).toBooleanProvider(false)
 
     fun preserveWorkingDir(providers: ProviderFactory): Provider<Boolean> =
-        providers.findProperty(PRESERVE_WD).toBooleanProvider(false)
+        providers.valueOrNull(PRESERVE_WD).toBooleanProvider(false)
 
     fun macSign(providers: ProviderFactory): Provider<Boolean> =
-        providers.findProperty(MAC_SIGN).toBooleanProvider(false)
+        providers.valueOrNull(MAC_SIGN).toBooleanProvider(false)
 
     fun macSignIdentity(providers: ProviderFactory): Provider<String?> =
-        providers.findProperty(MAC_SIGN_ID)
+        providers.valueOrNull(MAC_SIGN_ID)
 
     fun macSignKeychain(providers: ProviderFactory): Provider<String?> =
-        providers.findProperty(MAC_SIGN_KEYCHAIN)
+        providers.valueOrNull(MAC_SIGN_KEYCHAIN)
 
     fun macSignPrefix(providers: ProviderFactory): Provider<String?> =
-        providers.findProperty(MAC_SIGN_PREFIX)
+        providers.valueOrNull(MAC_SIGN_PREFIX)
 
     fun macNotarizationAppleID(providers: ProviderFactory): Provider<String?> =
-        providers.findProperty(MAC_NOTARIZATION_APPLE_ID)
+        providers.valueOrNull(MAC_NOTARIZATION_APPLE_ID)
 
     fun macNotarizationPassword(providers: ProviderFactory): Provider<String?> =
-        providers.findProperty(MAC_NOTARIZATION_PASSWORD)
+        providers.valueOrNull(MAC_NOTARIZATION_PASSWORD)
 
     fun macNotarizationAscProvider(providers: ProviderFactory): Provider<String?> =
-        providers.findProperty(MAC_NOTARIZATION_ASC_PROVIDER)
+        providers.valueOrNull(MAC_NOTARIZATION_ASC_PROVIDER)
 
     fun checkJdkVendor(providers: ProviderFactory): Provider<Boolean> =
-        providers.findProperty(CHECK_JDK_VENDOR).toBooleanProvider(true)
+        providers.valueOrNull(CHECK_JDK_VENDOR).toBooleanProvider(true)
 }
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt
new file mode 100644
index 00000000000..55e3a440a09
--- /dev/null
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
+ */
+
+package org.jetbrains.compose.experimental.internal
+
+import org.gradle.api.Project
+import org.jetbrains.compose.ComposeMultiplatformBuildService
+import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
+import org.jetbrains.compose.internal.mppExt
+import org.jetbrains.compose.internal.utils.KGPPropertyProvider
+import org.jetbrains.compose.internal.utils.configureEachWithType
+import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
+import org.jetbrains.kotlin.konan.target.presetName
+
+private const val PROJECT_CACHE_KIND_PROPERTY_NAME = "kotlin.native.cacheKind"
+private const val COMPOSE_NATIVE_MANAGE_CACHE_KIND = "compose.kotlin.native.manageCacheKind"
+private const val NONE_VALUE = "none"
+
+internal fun Project.configureNativeCompilerCaching() {
+    if (findProperty(COMPOSE_NATIVE_MANAGE_CACHE_KIND) == "false") return
+
+    plugins.withId(KOTLIN_MPP_PLUGIN_ID) {
+        val kotlinVersion = kotlinVersionNumbers(this)
+        mppExt.targets.configureEachWithType<KotlinNativeTarget> {
+            checkCacheKindUserValueIsNotNone()
+            configureTargetCompilerCache(kotlinVersion)
+        }
+    }
+}
+
+private fun KotlinNativeTarget.checkCacheKindUserValueIsNotNone() {
+    // To determine cache kind KGP checks kotlin.native.cacheKind.<PRESET_NAME> first, then kotlin.native.cacheKind
+    // For each property it tries to read Project.property, then checks local.properties
+    // See https://github.com/JetBrains/kotlin/blob/d4d30dcfcf1afb083f09279c6f1ba05031efeabb/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt#L416
+    val cacheKindProperties = listOf(targetCacheKindPropertyName, PROJECT_CACHE_KIND_PROPERTY_NAME)
+    val propertyProviders = listOf(
+        KGPPropertyProvider.GradleProperties(project),
+        KGPPropertyProvider.LocalProperties(project)
+    )
+
+    for (cacheKindProperty in cacheKindProperties) {
+        for (provider in propertyProviders) {
+            val value = provider.valueOrNull(cacheKindProperty)
+            if (value != null) {
+                if (value.equals(NONE_VALUE, ignoreCase = true)) {
+                    ComposeMultiplatformBuildService
+                        .getInstance(project)
+                        .warnOnceAfterBuild(cacheKindPropertyWarningMessage(cacheKindProperty, provider))
+                }
+                return
+            }
+        }
+    }
+}
+
+private fun cacheKindPropertyWarningMessage(
+    cacheKindProperty: String,
+    provider: KGPPropertyProvider
+) = """
+    |Warning: '$cacheKindProperty' is explicitly set to `none`.
+    |This option significantly slows the Kotlin/Native compiler.
+    |Compose Multiplatform Gradle plugin can set this property automatically,
+    |when it is necessary.
+    |  * Recommended action: remove explicit '$cacheKindProperty=$NONE_VALUE' from ${provider.location}. 
+    |  * Alternative action: if you are sure you need '$cacheKindProperty=$NONE_VALUE', disable
+    |this warning by adding '$COMPOSE_NATIVE_MANAGE_CACHE_KIND=false' to your 'gradle.properties'.
+""".trimMargin()
+
+private fun KotlinNativeTarget.configureTargetCompilerCache(kotlinVersion: KotlinVersion) {
+    // See comments in https://youtrack.jetbrains.com/issue/KT-57329
+    when {
+        // Kotlin < 1.9.0 => disable cache
+        kotlinVersion < KotlinVersion(1, 9, 0) -> {
+            disableKotlinNativeCache()
+        }
+        // 1.9.0 <= Kotlin < 1.9.20 => add -Xlazy-ir-for-caches=disable
+        kotlinVersion < KotlinVersion(1, 9, 20) -> {
+            disableLazyIrForCaches()
+        }
+        // Kotlin >= 1.9.20 => do nothing
+        else -> {}
+    }
+}
+
+private val KotlinNativeTarget.targetCacheKindPropertyName: String
+    get() = "$PROJECT_CACHE_KIND_PROPERTY_NAME.${konanTarget.presetName}"
+
+private fun KotlinNativeTarget.disableKotlinNativeCache() {
+    if (project.hasProperty(targetCacheKindPropertyName)) {
+        project.setProperty(targetCacheKindPropertyName, NONE_VALUE)
+    } else {
+        project.extensions.extraProperties.set(targetCacheKindPropertyName, NONE_VALUE)
+    }
+}
+
+private fun KotlinNativeTarget.disableLazyIrForCaches() {
+    compilations.configureEach { compilation ->
+        compilation.kotlinOptions.freeCompilerArgs += listOf("-Xlazy-ir-for-caches=disable")
+    }
+}
+
+private fun kotlinVersionNumbers(project: Project): KotlinVersion {
+    val version = project.getKotlinPluginVersion()
+    val m = Regex("(\\d+)\\.(\\d+)\\.(\\d+)").find(version) ?: error("Kotlin version has unexpected format: '$version'")
+    val (_, majorPart, minorPart, patchPart) = m.groupValues
+    return KotlinVersion(
+        major = majorPart.toIntOrNull() ?: error("Could not parse major part '$majorPart' of Kotlin plugin version: '$version'"),
+        minor = minorPart.toIntOrNull() ?: error("Could not parse minor part '$minorPart' of Kotlin plugin version: '$version'"),
+        patch = patchPart.toIntOrNull() ?: error("Could not parse patch part '$patchPart' of Kotlin plugin version: '$version'"),
+    )
+}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/utils/IosGradleProperties.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/utils/IosGradleProperties.kt
index bfacedbf39e..fa5829b21d4 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/utils/IosGradleProperties.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/utils/IosGradleProperties.kt
@@ -7,12 +7,12 @@ package org.jetbrains.compose.experimental.uikit.internal.utils
 
 import org.gradle.api.provider.Provider
 import org.gradle.api.provider.ProviderFactory
-import org.jetbrains.compose.internal.utils.findProperty
+import org.jetbrains.compose.internal.utils.valueOrNull
 import org.jetbrains.compose.internal.utils.toBooleanProvider
 
 internal object IosGradleProperties {
     const val SYNC_RESOURCES_PROPERTY = "org.jetbrains.compose.ios.resources.sync"
 
     fun syncResources(providers: ProviderFactory): Provider<Boolean> =
-        providers.findProperty(SYNC_RESOURCES_PROPERTY).toBooleanProvider(true)
+        providers.valueOrNull(SYNC_RESOURCES_PROPERTY).toBooleanProvider(true)
 }
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/BuildEventsListenerRegistryProvider.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/BuildEventsListenerRegistryProvider.kt
new file mode 100644
index 00000000000..5aeab21c98a
--- /dev/null
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/BuildEventsListenerRegistryProvider.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
+ */
+
+package org.jetbrains.compose.internal.utils
+
+import org.gradle.api.Project
+import org.gradle.build.event.BuildEventsListenerRegistry
+import javax.inject.Inject
+
+@Suppress("UnstableApiUsage")
+internal abstract class BuildEventsListenerRegistryProvider @Inject constructor(val registry: BuildEventsListenerRegistry) {
+    companion object {
+        fun getInstance(project: Project): BuildEventsListenerRegistry =
+            project.objects.newInstance(BuildEventsListenerRegistryProvider::class.java).registry
+    }
+}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt
new file mode 100644
index 00000000000..bef38b4a257
--- /dev/null
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
+ */
+
+package org.jetbrains.compose.internal.utils
+
+import org.gradle.api.Project
+import java.util.*
+
+/**
+ * Reads Kotlin Gradle plugin properties.
+ *
+ * Kotlin Gradle plugin supports reading property from two sources:
+ * 1. Gradle properties. Normally located in gradle.properties file,
+ * but can also be provided via command-line, <GRADLE_HOME>/gradle.properties
+ * or can be set via Gradle API.
+ * 2. local.properties file. local.properties file is not supported by Gradle out-of-the-box.
+ * Nevertheless, it became a widespread convention.
+ */
+internal abstract class KGPPropertyProvider {
+    abstract fun valueOrNull(propertyName: String): String?
+    abstract val location: String
+
+    class GradleProperties(private val project: Project) : KGPPropertyProvider() {
+        override fun valueOrNull(propertyName: String): String? = project.findProperty(propertyName)?.toString()
+        override val location: String = "gradle.properties"
+    }
+
+    class LocalProperties(project: Project) : KGPPropertyProvider() {
+        private val localProperties: Properties by lazyLoadProperties(project.localPropertiesFile)
+        override fun valueOrNull(propertyName: String): String? = localProperties.getProperty(propertyName)
+        override val location: String = "local.properties"
+    }
+}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/fileUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/fileUtils.kt
index ed4dcec5ed2..cb1cc91f4b7 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/fileUtils.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/fileUtils.kt
@@ -12,6 +12,7 @@ import org.gradle.api.file.FileSystemOperations
 import org.gradle.api.file.RegularFile
 import org.gradle.api.provider.Provider
 import java.io.File
+import java.util.*
 
 internal fun Provider<String>.toDir(project: Project): Provider<Directory> =
     project.layout.dir(map { File(it) })
@@ -55,4 +56,14 @@ internal fun FileSystemOperations.clearDirs(vararg dirs: Provider<out FileSystem
 }
 
 private fun Array<out Provider<out FileSystemLocation>>.ioFiles(): Array<File> =
-    let { providers -> Array(size) { i -> providers[i].ioFile } }
\ No newline at end of file
+    let { providers -> Array(size) { i -> providers[i].ioFile } }
+
+internal fun lazyLoadProperties(propertiesFile: File): Lazy<Properties> = lazy {
+    Properties().apply {
+        if (propertiesFile.isFile) {
+            propertiesFile.inputStream().use {
+                load(it)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt
index 570262ba67b..4b418218910 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/gradleUtils.kt
@@ -5,6 +5,7 @@
 
 package org.jetbrains.compose.internal.utils
 
+import org.gradle.api.DomainObjectCollection
 import org.gradle.api.Project
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.logging.Logger
@@ -61,3 +62,13 @@ internal fun Project.detachedDependency(
 
 internal fun Configuration.excludeTransitiveDependencies(): Configuration =
     apply { isTransitive = false }
+
+internal inline fun <reified SubT> DomainObjectCollection<*>.configureEachWithType(
+    crossinline fn: SubT.() -> Unit
+) {
+    configureEach {
+        if (it is SubT) {
+            it.fn()
+        }
+    }
+}
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/providerUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/providerUtils.kt
index 955b49dd140..07bd94dd3bc 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/providerUtils.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/providerUtils.kt
@@ -30,7 +30,7 @@ internal inline fun <reified T> Provider<T>.toProperty(objects: ObjectFactory):
 internal inline fun <reified T> Task.provider(noinline fn: () -> T): Provider<T> =
     project.provider(fn)
 
-internal fun ProviderFactory.findProperty(prop: String): Provider<String?> =
+internal fun ProviderFactory.valueOrNull(prop: String): Provider<String?> =
     provider {
         gradleProperty(prop).forUseAtConfigurationTimeSafe().orNull
     }
diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt
index f32fde4f746..f2cd0db71a2 100644
--- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt
+++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt
@@ -108,6 +108,61 @@ class GradlePluginTest : GradlePluginTestBase() {
         }
     }
 
+    @Test
+    fun nativeCacheKind() {
+        Assumptions.assumeTrue(currentOS == OS.MacOS)
+        fun nativeCacheKindProject(kotlinVersion: String) = testProject(
+            TestProjects.nativeCacheKind,
+            defaultTestEnvironment.copy(kotlinVersion = kotlinVersion, useGradleConfigurationCache = false)
+        )
+
+        val task = ":linkDebugFrameworkIosX64"
+        with(nativeCacheKindProject(kotlinVersion = TestKotlinVersions.v1_8_20)) {
+            gradle(task, "--info").checks {
+                check.taskSuccessful(task)
+                check.logDoesntContain("-Xauto-cache-from=")
+            }
+        }
+        testWorkDir.deleteRecursively()
+        testWorkDir.mkdirs()
+        with(nativeCacheKindProject(kotlinVersion = TestKotlinVersions.v1_9_0) ) {
+            gradle(task, "--info").checks {
+                check.taskSuccessful(task)
+                check.logContains("-Xauto-cache-from=")
+                check.logContains("-Xlazy-ir-for-caches=disable")
+            }
+        }
+    }
+
+    @Test
+    fun nativeCacheKindWarning() {
+        Assumptions.assumeTrue(currentOS == OS.MacOS)
+        fun nativeCacheKindWarningProject(kotlinVersion: String) = testProject(
+            TestProjects.nativeCacheKindWarning,
+            defaultTestEnvironment.copy(kotlinVersion = kotlinVersion)
+        )
+
+        val cacheKindWarning = "Warning: 'kotlin.native.cacheKind' is explicitly set to `none`"
+
+        val args = arrayOf("build", "--dry-run", "-Pkotlin.native.cacheKind=none")
+        with(nativeCacheKindWarningProject(kotlinVersion = TestKotlinVersions.v1_8_20)) {
+            gradle(*args).checks {
+                check.logContainsOnce(cacheKindWarning)
+            }
+            // check that the warning is shown even when the configuration is loaded from cache
+            gradle(*args).checks {
+                check.logContainsOnce(cacheKindWarning)
+            }
+        }
+        testWorkDir.deleteRecursively()
+        testWorkDir.mkdirs()
+        with(nativeCacheKindWarningProject(kotlinVersion = TestKotlinVersions.v1_9_0) ) {
+            gradle(*args).checks {
+                check.logContainsOnce(cacheKindWarning)
+            }
+        }
+    }
+
     @Test
     fun skikoWasm() = with(
         testProject(
diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestKotlinVersions.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestKotlinVersions.kt
index bfa4c103363..5dc8a2c5558 100644
--- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestKotlinVersions.kt
+++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestKotlinVersions.kt
@@ -7,4 +7,6 @@ package org.jetbrains.compose.test.utils
 
 object TestKotlinVersions {
     val Default = TestProperties.composeCompilerCompatibleKotlinVersion
+    val v1_8_20 = "1.8.20"
+    val v1_9_0 = "1.9.0"
 }
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt
index 8ca826726a4..b2a84f49ba7 100644
--- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt
+++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt
@@ -26,4 +26,6 @@ object TestProjects {
     const val jvmPreview = "misc/jvmPreview"
     const val iosResources = "misc/iosResources"
     const val iosMokoResources = "misc/iosMokoResources"
+    const val nativeCacheKind = "misc/nativeCacheKind"
+    const val nativeCacheKindWarning = "misc/nativeCacheKindWarning"
 }
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt
index 955616674cc..6be9978bb01 100644
--- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt
+++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt
@@ -29,6 +29,14 @@ internal class BuildResultChecks(private val result: BuildResult) {
     val log: String
         get() = result.output
 
+    fun logContainsOnce(substring: String) {
+        val actualCount = log.countOccurrencesOf(substring)
+        if (actualCount != 1) throw AssertionError(
+            "Test output must contain substring '$substring' exactly once. " +
+                    "Actual number of occurrences: $actualCount"
+        )
+    }
+
     fun logContains(substring: String) {
         if (!result.output.contains(substring)) {
             throw AssertionError("Test output does not contain the expected string: '$substring'")
@@ -96,3 +104,17 @@ internal fun assertNotEqualTextFiles(actual: File, expected: File) {
 
 private fun File.normalizedText() =
     readLines().joinToString("\n") { it.trim() }
+
+private fun String.countOccurrencesOf(substring: String): Int {
+    var count = 0
+    var i = 0
+    while (i >= 0 && i < length) {
+        i = indexOf(substring, startIndex = i)
+
+        if (i == -1) break
+
+        i++
+        count++
+    }
+    return count
+}
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle
new file mode 100644
index 00000000000..1e43a147b8e
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle
@@ -0,0 +1,27 @@
+plugins {
+    id "org.jetbrains.kotlin.multiplatform"
+    id "org.jetbrains.compose"
+}
+
+kotlin {
+    iosX64 {
+        binaries.framework {
+            isStatic = true
+            baseName = "shared"
+        }
+    }
+    iosArm64 {
+        binaries.framework {
+            isStatic = true
+            baseName = "shared"
+        }
+    }
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation(compose.runtime)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties
new file mode 100644
index 00000000000..689880ee3f4
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties
@@ -0,0 +1 @@
+org.jetbrains.compose.experimental.uikit.enabled=true
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle
new file mode 100644
index 00000000000..a270b9409b9
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle
@@ -0,0 +1,12 @@
+pluginManagement {
+    plugins {
+        id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER'
+        id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
+    }
+    repositories {
+        mavenLocal()
+        gradlePluginPortal()
+        maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
+    }
+}
+rootProject.name = "nativeCacheKind"
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt
new file mode 100644
index 00000000000..2fe6d0719f4
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt
@@ -0,0 +1,10 @@
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+
+@Composable
+fun App() {
+    var text by remember { mutableStateOf("Hello, World!") }
+}
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/build.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/build.gradle
new file mode 100644
index 00000000000..40170b68cb3
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/build.gradle
@@ -0,0 +1,20 @@
+plugins {
+    id "org.jetbrains.kotlin.multiplatform"
+    id "org.jetbrains.compose"
+}
+
+kotlin {
+    iosX64()
+    iosArm64()
+    iosSimulatorArm64()
+    macosX64()
+    macosArm64()
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation(compose.runtime)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/gradle.properties
new file mode 100644
index 00000000000..689880ee3f4
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/gradle.properties
@@ -0,0 +1 @@
+org.jetbrains.compose.experimental.uikit.enabled=true
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/settings.gradle b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/settings.gradle
new file mode 100644
index 00000000000..a270b9409b9
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/settings.gradle
@@ -0,0 +1,12 @@
+pluginManagement {
+    plugins {
+        id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER'
+        id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
+    }
+    repositories {
+        mavenLocal()
+        gradlePluginPortal()
+        maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
+    }
+}
+rootProject.name = "nativeCacheKind"
\ No newline at end of file
diff --git a/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/src/commonMain/kotlin/App.kt
new file mode 100644
index 00000000000..2fe6d0719f4
--- /dev/null
+++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKindWarning/src/commonMain/kotlin/App.kt
@@ -0,0 +1,10 @@
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+
+@Composable
+fun App() {
+    var text by remember { mutableStateOf("Hello, World!") }
+}