From 1939de0dc91499a378561dde8607e0ab2d63ec0f Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Wed, 9 Aug 2023 14:25:35 +0200 Subject: [PATCH] Set Kotlin/Native cache kind based on Kotlin version Resolves #2046 Resolves #2386 --- .../org/jetbrains/compose/ComposePlugin.kt | 3 + .../configureNativeCompilerCaching.kt | 91 +++++++++++++++++++ .../internal/utils/KGPPropertyFinder.kt | 35 +++++++ .../compose/internal/utils/fileUtils.kt | 13 ++- .../compose/internal/utils/gradleUtils.kt | 11 +++ .../tests/integration/GradlePluginTest.kt | 25 +++++ .../compose/test/utils/TestKotlinVersions.kt | 2 + .../compose/test/utils/TestProjects.kt | 1 + .../misc/nativeCacheKind/build.gradle | 29 ++++++ .../misc/nativeCacheKind/gradle.properties | 1 + .../misc/nativeCacheKind/settings.gradle | 12 +++ .../src/commonMain/kotlin/App.kt | 21 +++++ 12 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyFinder.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/gradle.properties create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/settings.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt 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..1fd2edcf6bd 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 @@ -32,6 +33,7 @@ import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import java.util.* internal val composeVersion get() = ComposeBuildConfig.composeVersion @@ -52,6 +54,7 @@ class ComposePlugin : Plugin { 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/experimental/internal/configureNativeCompilerCaching.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt new file mode 100644 index 00000000000..bd247a14e35 --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt @@ -0,0 +1,91 @@ +/* + * 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.internal.KOTLIN_MPP_PLUGIN_ID +import org.jetbrains.compose.internal.mppExt +import org.jetbrains.compose.internal.utils.KGPPropertyFinder +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 KOTLIN_NATIVE_CACHE_KIND = "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 propertyFinders = listOf( + KGPPropertyFinder.GradleProperties(this), + KGPPropertyFinder.LocalProperties(this) + ) + + val (majorKotlinVer, minorKotlinVer) = kotlinVersionNumbers(this) + + val isKotlinVersionAtLeast19 = majorKotlinVer > 1 || (majorKotlinVer == 1 && minorKotlinVer >= 9) + val isKotlinVersionBefore19 = !isKotlinVersionAtLeast19 + + mppExt.targets.configureEachWithType { + configureCompilerCache(propertyFinders, isKotlinVersionBefore19) + } + } +} + +private fun KotlinNativeTarget.configureCompilerCache( + propertyProviders: List, + isKotlinVersionBefore19: Boolean +) { + fun String?.isNone(): Boolean = + NONE_VALUE.equals(this, ignoreCase = true) + + val targetCacheKindProperty = "$KOTLIN_NATIVE_CACHE_KIND.${konanTarget.presetName}" + for (cacheKindProperty in listOf(targetCacheKindProperty, KOTLIN_NATIVE_CACHE_KIND)) { + for (provider in propertyProviders) { + val value = provider.findProperty(cacheKindProperty) + if (value.isNone()) { + project.logger.warn( + """ + |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' from ${provider.location}. + | * Alternative action: if you are sure you need '$cacheKindProperty=none', disable + |this warning by adding '$COMPOSE_NATIVE_MANAGE_CACHE_KIND=false' to your 'gradle.properties'. + """.trimMargin() + ) + } + + if (value != null) break + } + } + + if (isKotlinVersionBefore19) { + if (project.hasProperty(targetCacheKindProperty)) { + project.setProperty(targetCacheKindProperty, NONE_VALUE) + } else { + project.extensions.extraProperties.set(targetCacheKindProperty, NONE_VALUE) + } + } +} + +private data class KotlinVersionNumbers(val major: Int, val minor: Int) + +private fun kotlinVersionNumbers(project: Project): KotlinVersionNumbers { + val version = project.getKotlinPluginVersion() + val versionNumbers = version.trim() + .split(".") + .take(2) + val majorPart = versionNumbers.getOrNull(0) + val minorPart = versionNumbers.getOrNull(1) + return KotlinVersionNumbers( + 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'"), + ) +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyFinder.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyFinder.kt new file mode 100644 index 00000000000..2db00a8db3a --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyFinder.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.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 KGPPropertyFinder { + abstract fun findProperty(propertyName: String): String? + abstract val location: String + + class GradleProperties(private val project: Project) : KGPPropertyFinder() { + override fun findProperty(propertyName: String): String? = project.findProperty(propertyName)?.toString() + override val location: String = "gradle.properties" + } + + class LocalProperties(project: Project) : KGPPropertyFinder() { + private val localProperties: Properties by lazyLoadProperties(project.localPropertiesFile) + override fun findProperty(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.toDir(project: Project): Provider = project.layout.dir(map { File(it) }) @@ -55,4 +56,14 @@ internal fun FileSystemOperations.clearDirs(vararg dirs: Provider>.ioFiles(): Array = - 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 = 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 DomainObjectCollection<*>.configureEachWithType( + crossinline fn: SubT.() -> Unit +) { + configureEach { + if (it is SubT) { + it.fn() + } + } +} 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..45bb31f9beb 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,31 @@ 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=") + } + } + } + @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..1b516f6f2ca 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,5 @@ object TestProjects { const val jvmPreview = "misc/jvmPreview" const val iosResources = "misc/iosResources" const val iosMokoResources = "misc/iosMokoResources" + const val nativeCacheKind = "misc/nativeCacheKind" } \ No newline at end of file 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..08bcd031571 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/build.gradle @@ -0,0 +1,29 @@ +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) + implementation(compose.material) + implementation(compose.foundation) + } + } + } +} \ 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..1e3f0b616e2 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/nativeCacheKind/src/commonMain/kotlin/App.kt @@ -0,0 +1,21 @@ +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +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!") } + + MaterialTheme { + Button(onClick = { + text = "Hello, Desktop!" + }) { + Text(text) + } + } +}