From eb124feb4bc3536a00388e20c4a66e680400c5fe Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov <654232+AlexeyTsvetkov@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:58:21 +0300 Subject: [PATCH] Fix reporting configuration problems with configuration cache (#3596) Sometimes we need to report warnings during the configuration phase. For example, when Androidx Compose Compiler is used with non-JVM targets (e.g. iOS/js), we want to warn users that using non-JB compiler with non-JVM targets is not supported. The default way of reporting warnings in Gradle is using a logger. For example we could write something like: ``` abstract class ComposePlugin : Plugin { override fun apply(project: Project) { if (project.hasNonJvmTargets() && project.usesNonJBComposeCompiler()) { project.logger.warn("...") } } } ``` This approach has a few issues: 1. When the Configuration Cache is enabled, project's configuration might get skipped altogether, and the warning won't be printed. 2. If a project contains multiple Gradle modules (subprojects), the warning might be printed multiple times. That might be OK for some warnings. But repeating exactly the same warning 10s or 100s is unnecessary. The only way to share the state between Gradle modules, while preserving compatibility with the Configuration Cache, is to define Gradle Build Service. In 1.5.0 we used Gradle Build Service mechanism for both warnings. However, I did not know that: * only the service's parameters are persisted in the Configuration Cache. The service itself is not persisted. * if a service instance is materialized during the configuration phase, then all changes made to its parameters will not be visible to that particular instance (but they will be visible to the next instance). So the only way to report diagnostics with configuration cache without repetition is to define a service that is not materialized during the configuration phase (i.e. serviceProvider.get() is not called), add to add warnings to a set property of the service. This change implements that. Resolves #3595 --- .../ComposeCompilerKotlinSupportPlugin.kt | 17 ++- .../ComposeMultiplatformBuildService.kt | 119 ------------------ .../org/jetbrains/compose/ComposePlugin.kt | 9 +- .../configureNativeCompilerCaching.kt | 11 +- ...bstractComposeMultiplatformBuildService.kt | 81 ++++++++++++ .../BuildEventsListenerRegistryProvider.kt | 3 +- .../ConfigurationProblemReporterService.kt | 66 ++++++++++ .../service/GradlePropertySnapshotService.kt | 49 ++++++++ .../internal/utils/KGPPropertyProvider.kt | 7 +- .../tests/integration/GradlePluginTest.kt | 7 +- .../UnsupportedCompilerPluginWarningTest.kt | 58 +++++---- .../compose/test/utils/TestProjects.kt | 1 + .../build.gradle | 39 ++++++ .../gradle.properties | 2 + .../settings.gradle | 11 ++ .../src/commonMain/kotlin/App.kt | 19 +++ 16 files changed, 332 insertions(+), 167 deletions(-) delete mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt rename gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/{utils => service}/BuildEventsListenerRegistryProvider.kt (82%) create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties create mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt 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 3d9c326490..996590487c 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 @@ -7,18 +7,14 @@ package org.jetbrains.compose import org.gradle.api.Project import org.gradle.api.provider.Provider -import org.gradle.build.event.BuildEventsListenerRegistry import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider import org.jetbrains.compose.internal.mppExtOrNull +import org.jetbrains.compose.internal.service.ConfigurationProblemReporterService import org.jetbrains.compose.internal.webExt import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget -import javax.inject.Inject -@Suppress("UnstableApiUsage") -class ComposeCompilerKotlinSupportPlugin @Inject constructor( - @Suppress("UnstableApiUsage") private val buildEventsListenerRegistry: BuildEventsListenerRegistry -) : KotlinCompilerPluginSupportPlugin { +class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin { private lateinit var composeCompilerArtifactProvider: ComposeCompilerArtifactProvider override fun apply(target: Project) { @@ -35,7 +31,7 @@ class ComposeCompilerKotlinSupportPlugin @Inject constructor( } } - private fun collectUnsupportedCompilerPluginUsages(target: Project) { + private fun collectUnsupportedCompilerPluginUsages(project: Project) { fun Project.hasNonJvmTargets(): Boolean { val nonJvmTargets = setOf(KotlinPlatformType.native, KotlinPlatformType.js, KotlinPlatformType.wasm) return mppExtOrNull?.targets?.any { @@ -47,10 +43,11 @@ class ComposeCompilerKotlinSupportPlugin @Inject constructor( return !groupId.startsWith("org.jetbrains.compose.compiler") } - ComposeMultiplatformBuildService.getInstance(target).unsupportedCompilerPlugins.add( - target.provider { + ConfigurationProblemReporterService.registerUnsupportedPluginProvider( + project, + project.provider { composeCompilerArtifactProvider.compilerArtifact.takeIf { - target.hasNonJvmTargets() && it.isNonJBComposeCompiler() + project.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 deleted file mode 100644 index 85bf9b673b..0000000000 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt +++ /dev/null @@ -1,119 +0,0 @@ -package org.jetbrains.compose - -import org.gradle.api.Project -import org.gradle.api.logging.Logging -import org.gradle.api.provider.MapProperty -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.tooling.events.FinishEvent -import org.gradle.tooling.events.OperationCompletionListener -import org.jetbrains.compose.experimental.internal.SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES -import org.jetbrains.compose.internal.utils.BuildEventsListenerRegistryProvider -import org.jetbrains.compose.internal.utils.loadProperties -import org.jetbrains.compose.internal.utils.localPropertiesFile -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, - OperationCompletionListener, AutoCloseable { - - abstract class Parameters : BuildServiceParameters { - abstract val gradlePropertiesCacheKindSnapshot: MapProperty - abstract val localPropertiesCacheKindSnapshot: MapProperty - } - - private val log = Logging.getLogger(this.javaClass) - - internal abstract val unsupportedCompilerPlugins: SetProperty> - internal abstract val delayedWarnings: SetProperty - internal val gradlePropertiesSnapshot: Map = parameters.gradlePropertiesCacheKindSnapshot.get() - internal val localPropertiesSnapshot: Map = parameters.localPropertiesCacheKindSnapshot.get() - - fun warnOnceAfterBuild(message: String) { - delayedWarnings.add(message) - } - - override fun close() { - notifyAboutUnsupportedCompilerPlugin() - logDelayedWarnings() - } - - private fun notifyAboutUnsupportedCompilerPlugin() { - val unsupportedCompilerPlugin = unsupportedCompilerPlugins.orNull - ?.firstOrNull() - ?.orNull - - if (unsupportedCompilerPlugin != null) { - log.error(createWarningAboutNonCompatibleCompiler(unsupportedCompilerPlugin.groupId)) - } - } - - private fun logDelayedWarnings() { - for (warning in delayedWarnings.get()) { - log.warn(warning) - } - } - - override fun onFinish(event: FinishEvent) {} - - companion object { - 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 - } - - return null - } - - fun getInstance(project: Project): ComposeMultiplatformBuildService = - findExistingComposeService(project) ?: error("ComposeMultiplatformBuildService was not initialized!") - - @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) { - it.parameters.initPropertiesSnapshots(project.rootProject) - } - // workaround to instanciate a service even if it not binded to a task - BuildEventsListenerRegistryProvider.getInstance(project).onTaskCompletion(newService) - } - - private fun Parameters.initPropertiesSnapshots(rootProject: Project) { - // we want to record original properties (explicitly set by a user) - // before we possibly change them in configureNativeCompilerCaching.kt - val localProperties = loadProperties(rootProject.localPropertiesFile) - for (cacheKindProperty in SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES) { - rootProject.findProperty(cacheKindProperty)?.toString()?.let { value -> - gradlePropertiesCacheKindSnapshot.put(cacheKindProperty, value) - } - localProperties[cacheKindProperty]?.toString()?.let { value -> - localPropertiesCacheKindSnapshot.put(cacheKindProperty, value) - } - } - } - } -} 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 625e6044e3..9b2a5c1fe7 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 @@ -26,6 +26,8 @@ import org.jetbrains.compose.experimental.uikit.internal.resources.configureSync import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.compose.internal.mppExt import org.jetbrains.compose.internal.mppExtOrNull +import org.jetbrains.compose.internal.service.ConfigurationProblemReporterService +import org.jetbrains.compose.internal.service.GradlePropertySnapshotService import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.web.WebExtension import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler @@ -36,9 +38,14 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType internal val composeVersion get() = ComposeBuildConfig.composeVersion +private fun initBuildServices(project: Project) { + ConfigurationProblemReporterService.init(project) + GradlePropertySnapshotService.init(project) +} + abstract class ComposePlugin : Plugin { override fun apply(project: Project) { - ComposeMultiplatformBuildService.init(project) + initBuildServices(project) val composeExtension = project.extensions.create("compose", ComposeExtension::class.java, project) val desktopExtension = composeExtension.extensions.create("desktop", DesktopExtension::class.java) 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 index b7c279d417..f9ed3ae82c 100644 --- 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 @@ -6,9 +6,9 @@ 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.service.ConfigurationProblemReporterService import org.jetbrains.compose.internal.utils.KGPPropertyProvider import org.jetbrains.compose.internal.utils.configureEachWithType import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget @@ -59,11 +59,10 @@ private fun KotlinNativeTarget.checkExplicitCacheKind() { for (provider in propertyProviders) { val value = provider.valueOrNull(cacheKindProperty) if (value != null) { - ComposeMultiplatformBuildService - .getInstance(project) - .warnOnceAfterBuild( - explicitCacheKindWarningMessage(cacheKindProperty, value, provider) - ) + ConfigurationProblemReporterService.reportProblem( + project, + explicitCacheKindWarningMessage(cacheKindProperty, value, provider) + ) return } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt new file mode 100644 index 0000000000..8a987034be --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt @@ -0,0 +1,81 @@ +/* + * 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.service + +import org.gradle.api.Project +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters +import org.gradle.api.services.BuildServiceRegistration +import org.gradle.tooling.events.FinishEvent +import org.gradle.tooling.events.OperationCompletionListener + +// The service implements OperationCompletionListener just so Gradle would materialize the service even if the service is not used by any task or transformation + abstract class AbstractComposeMultiplatformBuildService

: BuildService

, OperationCompletionListener, AutoCloseable { + override fun onFinish(event: FinishEvent) {} + override fun close() {} +} + +internal inline fun > serviceName(instance: Service? = null): String = + fqName(instance) + +internal inline fun , reified Params : BuildServiceParameters> registerServiceIfAbsent( + project: Project, + crossinline initParams: Params.() -> Unit = {} +): BuildServiceRegistration { + if (findRegistration(project) == null) { + val newService = project.gradle.sharedServices.registerIfAbsent(fqName(), Service::class.java) { + it.parameters.initParams() + } + // Workaround to materialize a service even if it is not bound to a task + BuildEventsListenerRegistryProvider.getInstance(project).onTaskCompletion(newService) + } + + return getExistingServiceRegistration(project) +} + +internal inline fun , reified Params : BuildServiceParameters> getExistingServiceRegistration( + project: Project +): BuildServiceRegistration { + val registration = findRegistration(project) + ?: error("Service '${serviceName()}' was not initialized") + return registration.verified(project) +} + +private inline fun , reified Params : BuildServiceParameters> BuildServiceRegistration<*, *>.verified( + project: Project +): BuildServiceRegistration { + val parameters = parameters + // We are checking the type of parameters instead of the type of service + // to avoid materializing the service. + // After a service instance is created all changes made to its parameters won't be visible to + // that particular service instance. + // This is undesirable in some cases. For example, when reporting configuration problems, + // we want to collect all configuration issues from all projects first, then report issues all at once + // in execution phase. + if (parameters !is Params) { + // Compose Gradle plugin was probably loaded more than once + // See https://github.com/JetBrains/compose-multiplatform/issues/3459 + if (fqName(parameters) == fqName()) { + 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 '${serviceName()}' parameters have unexpected type: ${fqName(parameters)}") + } + } + + @Suppress("UNCHECKED_CAST") + return this as BuildServiceRegistration +} + +private inline fun , reified P : BuildServiceParameters> findRegistration( + project: Project +): BuildServiceRegistration<*, *>? = + project.gradle.sharedServices.registrations.findByName(fqName()) + +private inline fun fqName(instance: T? = null) = T::class.java.canonicalName 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/service/BuildEventsListenerRegistryProvider.kt similarity index 82% rename from gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/BuildEventsListenerRegistryProvider.kt rename to gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/BuildEventsListenerRegistryProvider.kt index 5aeab21c98..8473334131 100644 --- 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/service/BuildEventsListenerRegistryProvider.kt @@ -3,12 +3,13 @@ * 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 +package org.jetbrains.compose.internal.service import org.gradle.api.Project import org.gradle.build.event.BuildEventsListenerRegistry import javax.inject.Inject +// a hack to get BuildEventsListenerRegistry conveniently, which can only be injected by Gradle @Suppress("UnstableApiUsage") internal abstract class BuildEventsListenerRegistryProvider @Inject constructor(val registry: BuildEventsListenerRegistry) { companion object { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt new file mode 100644 index 0000000000..a13cf45a6d --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt @@ -0,0 +1,66 @@ +/* + * 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.service + +import org.gradle.api.Project +import org.gradle.api.logging.Logging +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty +import org.gradle.api.services.BuildServiceParameters +import org.jetbrains.compose.createWarningAboutNonCompatibleCompiler +import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact + +abstract class ConfigurationProblemReporterService : AbstractComposeMultiplatformBuildService() { + interface Parameters : BuildServiceParameters { + val unsupportedPluginWarningProviders: ListProperty> + val warnings: SetProperty + } + + private val log = Logging.getLogger(this.javaClass) + + override fun close() { + warnAboutUnsupportedCompilerPlugin() + logWarnings() + } + + private fun warnAboutUnsupportedCompilerPlugin() { + for (warningProvider in parameters.unsupportedPluginWarningProviders.get()) { + val warning = warningProvider.orNull + if (warning != null) { + log.warn(warning) + } + } + } + + private fun logWarnings() { + for (warning in parameters.warnings.get()) { + log.warn(warning) + } + } + companion object { + fun init(project: Project) { + registerServiceIfAbsent(project) + } + + private inline fun configureParameters(project: Project, fn: Parameters.() -> Unit) { + getExistingServiceRegistration(project) + .parameters.fn() + } + + fun reportProblem(project: Project, message: String) { + configureParameters(project) { warnings.add(message) } + } + + fun registerUnsupportedPluginProvider(project: Project, unsupportedPlugin: Provider) { + configureParameters(project) { + unsupportedPluginWarningProviders.add(unsupportedPlugin.map { unsupportedCompiler -> + unsupportedCompiler?.groupId?.let { createWarningAboutNonCompatibleCompiler(it) } + }) + } + } + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt new file mode 100644 index 0000000000..fa2c9b0078 --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt @@ -0,0 +1,49 @@ +/* + * 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.service + +import org.gradle.api.Project +import org.gradle.api.provider.MapProperty +import org.gradle.api.services.BuildServiceParameters +import org.jetbrains.compose.experimental.internal.SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES +import org.jetbrains.compose.internal.utils.loadProperties +import org.jetbrains.compose.internal.utils.localPropertiesFile + +internal abstract class GradlePropertySnapshotService : AbstractComposeMultiplatformBuildService() { + interface Parameters : BuildServiceParameters { + val gradlePropertiesCacheKindSnapshot: MapProperty + val localPropertiesCacheKindSnapshot: MapProperty + } + + internal val gradleProperties: Map = parameters.gradlePropertiesCacheKindSnapshot.get() + internal val localProperties: Map = parameters.localPropertiesCacheKindSnapshot.get() + + companion object { + fun init(project: Project) { + registerServiceIfAbsent(project) { + initParams(project) + } + } + + fun getInstance(project: Project): GradlePropertySnapshotService = + getExistingServiceRegistration(project).service.get() + + private fun Parameters.initParams(project: Project) { + // we want to record original properties (explicitly set by a user) + // before we possibly change them in configureNativeCompilerCaching.kt + val rootProject = project.rootProject + val localProperties = loadProperties(rootProject.localPropertiesFile) + for (cacheKindProperty in SUPPORTED_NATIVE_CACHE_KIND_PROPERTIES) { + rootProject.findProperty(cacheKindProperty)?.toString()?.let { value -> + gradlePropertiesCacheKindSnapshot.put(cacheKindProperty, value) + } + localProperties[cacheKindProperty]?.toString()?.let { value -> + localPropertiesCacheKindSnapshot.put(cacheKindProperty, value) + } + } + } + } +} \ 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 index ffff63ce16..d0431a41fc 100644 --- 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 @@ -6,8 +6,7 @@ package org.jetbrains.compose.internal.utils import org.gradle.api.Project -import org.jetbrains.compose.ComposeMultiplatformBuildService -import java.util.* +import org.jetbrains.compose.internal.service.GradlePropertySnapshotService /** * Reads Kotlin Gradle plugin properties. @@ -25,13 +24,13 @@ internal abstract class KGPPropertyProvider { class GradleProperties(private val project: Project) : KGPPropertyProvider() { override fun valueOrNull(propertyName: String): String? = - ComposeMultiplatformBuildService.getInstance(project).gradlePropertiesSnapshot[propertyName] + GradlePropertySnapshotService.getInstance(project).gradleProperties[propertyName] override val location: String = "gradle.properties" } class LocalProperties(private val project: Project) : KGPPropertyProvider() { override fun valueOrNull(propertyName: String): String? = - ComposeMultiplatformBuildService.getInstance(project).localPropertiesSnapshot[propertyName] + GradlePropertySnapshotService.getInstance(project).localProperties[propertyName] override val location: String = "local.properties" } } \ No newline at end of file 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 09360ae7e4..4f6f62ddd4 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 @@ -149,7 +149,7 @@ class GradlePluginTest : GradlePluginTestBase() { } fun testKotlinVersion(kotlinVersion: String) { - val args = arrayOf("build", "--dry-run") + val args = arrayOf("help") val commonPartOfWarning = "Compose Multiplatform Gradle plugin manages this property automatically" withNativeCacheKindWarningProject(kotlinVersion = kotlinVersion) { gradle(*args).checks { @@ -162,6 +162,11 @@ class GradlePluginTest : GradlePluginTestBase() { check.logContainsOnce("Warning: 'kotlin.native.cacheKind' is explicitly set to 'none'") check.logContainsOnce(commonPartOfWarning) } + + gradle(*args, "-Pkotlin.native.cacheKind=none").checks { + check.logContainsOnce("Warning: 'kotlin.native.cacheKind' is explicitly set to 'none'") + check.logContainsOnce(commonPartOfWarning) + } } withNativeCacheKindWarningProject(kotlinVersion = kotlinVersion) { gradle(*args, "-Pkotlin.native.cacheKind=static").checks { diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt index c90acff31f..36ee19a06a 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt @@ -11,34 +11,42 @@ class UnsupportedCompilerPluginWarningTest : GradlePluginTestBase() { private val androidxComposeCompilerGroupId = "androidx.compose.compiler" private val androidxComposeCompilerPlugin = "$androidxComposeCompilerGroupId:compiler:1.4.8" - @Suppress("RedundantUnitExpression") - @Test - fun testKotlinJs_shows_warning_for_androidx_compose_compiler() = testProject( - TestProjects.customCompilerArgs, defaultTestEnvironment.copy( - kotlinVersion = "1.8.22", - composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"", - composeCompilerArgs = "\"suppressKotlinVersionCompatibilityCheck=1.8.22\"" - ) - ).let { - it.gradle(":compileKotlinJs").checks { - check.taskSuccessful(":compileKotlinJs") - check.logContains(createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId)) + private fun testCustomCompilerUnsupportedPlatformsWarning( + platforms: String, + warningIsExpected: Boolean + ) { + testProject( + TestProjects.customCompilerUnsupportedPlatformsWarning, defaultTestEnvironment.copy( + kotlinVersion = "1.8.22", + composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"", + ) + ).apply { + // repeat twice to check that configuration cache hit does not affect the result + repeat(2) { + gradle("-Pplatforms=$platforms").checks { + val warning = createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId) + if (warningIsExpected) { + check.logContainsOnce(warning) + } else { + check.logDoesntContain(warning) + } + } + } } - Unit } - @Suppress("RedundantUnitExpression") @Test - fun testKotlinJvm_doesnt_show_warning_for_androidx_compose_compiler() = testProject( - TestProjects.customCompiler, defaultTestEnvironment.copy( - kotlinVersion = "1.8.22", - composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"", - ) - ).let { - it.gradle(":run").checks { - check.taskSuccessful(":run") - check.logDoesntContain(createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId)) - } - Unit + fun testJs() { + testCustomCompilerUnsupportedPlatformsWarning("js", warningIsExpected = true) + } + + @Test + fun testIos() { + testCustomCompilerUnsupportedPlatformsWarning("ios", warningIsExpected = true) + } + + @Test + fun testJvm() { + testCustomCompilerUnsupportedPlatformsWarning("jvm", warningIsExpected = false) } } \ 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 b23fccb0b9..f29517b111 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 @@ -12,6 +12,7 @@ object TestProjects { const val proguard = "application/proguard" const val customCompiler = "application/custom-compiler" const val customCompilerArgs = "application/custom-compiler-args" + const val customCompilerUnsupportedPlatformsWarning = "application/customCompilerUnsupportedPlatformsWarning" const val jvmKotlinDsl = "application/jvmKotlinDsl" const val moduleClashCli = "application/moduleClashCli" const val javaLogger = "application/javaLogger" diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle new file mode 100644 index 0000000000..8b6820a3dd --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle @@ -0,0 +1,39 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.compose" +} + +repositories { + google() + jetbrainsCompose() +} + +kotlin { + def platforms = project.property("platforms").split(",") + if (platforms.contains("jvm")) { + jvm() + } + if (platforms.contains("js")) { + js(IR) { + browser() + binaries.executable() + } + } + if (platforms.contains("ios")) { + ios() + } + + sourceSets { + commonMain { + dependencies { + implementation compose.runtime + implementation compose.material + implementation compose.foundation + } + } + } +} + +compose { + kotlinCompilerPlugin.set(COMPOSE_COMPILER_PLUGIN_PLACEHOLDER) +} diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties new file mode 100644 index 0000000000..e209558321 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties @@ -0,0 +1,2 @@ +org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.uikit.enabled=true \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle new file mode 100644 index 0000000000..0a2b43ab6c --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle @@ -0,0 +1,11 @@ +pluginManagement { + plugins { + id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' + } + repositories { + mavenLocal() + gradlePluginPortal() + } +} +rootProject.name = "customCompilerUnsupportedPlatformsWarning" diff --git a/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt new file mode 100644 index 0000000000..79dcb39588 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt @@ -0,0 +1,19 @@ +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +@Composable +fun App() { + MaterialTheme { + var message by remember { mutableStateOf("Press the button!") } + + Button( + onClick = { message = "Welcome to Compose Multiplatform!" } + ) { + Text(message) + } + } +} \ No newline at end of file