Browse Source

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<Project> {
    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
pull/3628/head
Alexey Tsvetkov 1 year ago committed by GitHub
parent
commit
eb124feb4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt
  2. 119
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt
  3. 9
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt
  4. 11
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt
  5. 81
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/AbstractComposeMultiplatformBuildService.kt
  6. 3
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/BuildEventsListenerRegistryProvider.kt
  7. 66
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/ConfigurationProblemReporterService.kt
  8. 49
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/service/GradlePropertySnapshotService.kt
  9. 7
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt
  10. 7
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt
  11. 58
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt
  12. 1
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProjects.kt
  13. 39
      gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/build.gradle
  14. 2
      gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/gradle.properties
  15. 11
      gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/settings.gradle
  16. 19
      gradle-plugins/compose/src/test/test-projects/application/customCompilerUnsupportedPlatformsWarning/src/commonMain/kotlin/App.kt

17
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.Project
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.build.event.BuildEventsListenerRegistry
import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider
import org.jetbrains.compose.internal.mppExtOrNull import org.jetbrains.compose.internal.mppExtOrNull
import org.jetbrains.compose.internal.service.ConfigurationProblemReporterService
import org.jetbrains.compose.internal.webExt import org.jetbrains.compose.internal.webExt
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import javax.inject.Inject
@Suppress("UnstableApiUsage") class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
class ComposeCompilerKotlinSupportPlugin @Inject constructor(
@Suppress("UnstableApiUsage") private val buildEventsListenerRegistry: BuildEventsListenerRegistry
) : KotlinCompilerPluginSupportPlugin {
private lateinit var composeCompilerArtifactProvider: ComposeCompilerArtifactProvider private lateinit var composeCompilerArtifactProvider: ComposeCompilerArtifactProvider
override fun apply(target: Project) { 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 { fun Project.hasNonJvmTargets(): Boolean {
val nonJvmTargets = setOf(KotlinPlatformType.native, KotlinPlatformType.js, KotlinPlatformType.wasm) val nonJvmTargets = setOf(KotlinPlatformType.native, KotlinPlatformType.js, KotlinPlatformType.wasm)
return mppExtOrNull?.targets?.any { return mppExtOrNull?.targets?.any {
@ -47,10 +43,11 @@ class ComposeCompilerKotlinSupportPlugin @Inject constructor(
return !groupId.startsWith("org.jetbrains.compose.compiler") return !groupId.startsWith("org.jetbrains.compose.compiler")
} }
ComposeMultiplatformBuildService.getInstance(target).unsupportedCompilerPlugins.add( ConfigurationProblemReporterService.registerUnsupportedPluginProvider(
target.provider { project,
project.provider {
composeCompilerArtifactProvider.compilerArtifact.takeIf { composeCompilerArtifactProvider.compilerArtifact.takeIf {
target.hasNonJvmTargets() && it.isNonJBComposeCompiler() project.hasNonJvmTargets() && it.isNonJBComposeCompiler()
} }
} }
) )

119
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt

@ -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<ComposeMultiplatformBuildService.Parameters>,
OperationCompletionListener, AutoCloseable {
abstract class Parameters : BuildServiceParameters {
abstract val gradlePropertiesCacheKindSnapshot: MapProperty<String, String>
abstract val localPropertiesCacheKindSnapshot: MapProperty<String, String>
}
private val log = Logging.getLogger(this.javaClass)
internal abstract val unsupportedCompilerPlugins: SetProperty<Provider<SubpluginArtifact?>>
internal abstract val delayedWarnings: SetProperty<String>
internal val gradlePropertiesSnapshot: Map<String, String> = parameters.gradlePropertiesCacheKindSnapshot.get()
internal val localPropertiesSnapshot: Map<String, String> = 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)
}
}
}
}
}

9
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.KOTLIN_MPP_PLUGIN_ID
import org.jetbrains.compose.internal.mppExt import org.jetbrains.compose.internal.mppExt
import org.jetbrains.compose.internal.mppExtOrNull 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.internal.utils.currentTarget
import org.jetbrains.compose.web.WebExtension import org.jetbrains.compose.web.WebExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
@ -36,9 +38,14 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
internal val composeVersion get() = ComposeBuildConfig.composeVersion internal val composeVersion get() = ComposeBuildConfig.composeVersion
private fun initBuildServices(project: Project) {
ConfigurationProblemReporterService.init(project)
GradlePropertySnapshotService.init(project)
}
abstract class ComposePlugin : Plugin<Project> { abstract class ComposePlugin : Plugin<Project> {
override fun apply(project: Project) { override fun apply(project: Project) {
ComposeMultiplatformBuildService.init(project) initBuildServices(project)
val composeExtension = project.extensions.create("compose", ComposeExtension::class.java, project) val composeExtension = project.extensions.create("compose", ComposeExtension::class.java, project)
val desktopExtension = composeExtension.extensions.create("desktop", DesktopExtension::class.java) val desktopExtension = composeExtension.extensions.create("desktop", DesktopExtension::class.java)

11
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/internal/configureNativeCompilerCaching.kt

@ -6,9 +6,9 @@
package org.jetbrains.compose.experimental.internal package org.jetbrains.compose.experimental.internal
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.compose.ComposeMultiplatformBuildService
import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
import org.jetbrains.compose.internal.mppExt 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.KGPPropertyProvider
import org.jetbrains.compose.internal.utils.configureEachWithType import org.jetbrains.compose.internal.utils.configureEachWithType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
@ -59,11 +59,10 @@ private fun KotlinNativeTarget.checkExplicitCacheKind() {
for (provider in propertyProviders) { for (provider in propertyProviders) {
val value = provider.valueOrNull(cacheKindProperty) val value = provider.valueOrNull(cacheKindProperty)
if (value != null) { if (value != null) {
ComposeMultiplatformBuildService ConfigurationProblemReporterService.reportProblem(
.getInstance(project) project,
.warnOnceAfterBuild( explicitCacheKindWarningMessage(cacheKindProperty, value, provider)
explicitCacheKindWarningMessage(cacheKindProperty, value, provider) )
)
return return
} }
} }

81
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<P : BuildServiceParameters> : BuildService<P> , OperationCompletionListener, AutoCloseable {
override fun onFinish(event: FinishEvent) {}
override fun close() {}
}
internal inline fun <reified Service : BuildService<*>> serviceName(instance: Service? = null): String =
fqName(instance)
internal inline fun <reified Service : AbstractComposeMultiplatformBuildService<Params>, reified Params : BuildServiceParameters> registerServiceIfAbsent(
project: Project,
crossinline initParams: Params.() -> Unit = {}
): BuildServiceRegistration<Service, Params> {
if (findRegistration<Service, Params>(project) == null) {
val newService = project.gradle.sharedServices.registerIfAbsent(fqName<Service>(), 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 Service : BuildService<Params>, reified Params : BuildServiceParameters> getExistingServiceRegistration(
project: Project
): BuildServiceRegistration<Service, Params> {
val registration = findRegistration<Service, Params>(project)
?: error("Service '${serviceName<Service>()}' was not initialized")
return registration.verified(project)
}
private inline fun <reified Service : BuildService<Params>, reified Params : BuildServiceParameters> BuildServiceRegistration<*, *>.verified(
project: Project
): BuildServiceRegistration<Service, Params> {
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<Params>()) {
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<Service>()}' parameters have unexpected type: ${fqName(parameters)}")
}
}
@Suppress("UNCHECKED_CAST")
return this as BuildServiceRegistration<Service, Params>
}
private inline fun <reified S : BuildService<P>, reified P : BuildServiceParameters> findRegistration(
project: Project
): BuildServiceRegistration<*, *>? =
project.gradle.sharedServices.registrations.findByName(fqName<S>())
private inline fun <reified T : Any> fqName(instance: T? = null) = T::class.java.canonicalName

3
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/BuildEventsListenerRegistryProvider.kt → 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. * 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.api.Project
import org.gradle.build.event.BuildEventsListenerRegistry import org.gradle.build.event.BuildEventsListenerRegistry
import javax.inject.Inject import javax.inject.Inject
// a hack to get BuildEventsListenerRegistry conveniently, which can only be injected by Gradle
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
internal abstract class BuildEventsListenerRegistryProvider @Inject constructor(val registry: BuildEventsListenerRegistry) { internal abstract class BuildEventsListenerRegistryProvider @Inject constructor(val registry: BuildEventsListenerRegistry) {
companion object { companion object {

66
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<ConfigurationProblemReporterService.Parameters>() {
interface Parameters : BuildServiceParameters {
val unsupportedPluginWarningProviders: ListProperty<Provider<String?>>
val warnings: SetProperty<String>
}
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<ConfigurationProblemReporterService, Parameters>(project)
}
private inline fun configureParameters(project: Project, fn: Parameters.() -> Unit) {
getExistingServiceRegistration<ConfigurationProblemReporterService, Parameters>(project)
.parameters.fn()
}
fun reportProblem(project: Project, message: String) {
configureParameters(project) { warnings.add(message) }
}
fun registerUnsupportedPluginProvider(project: Project, unsupportedPlugin: Provider<SubpluginArtifact?>) {
configureParameters(project) {
unsupportedPluginWarningProviders.add(unsupportedPlugin.map { unsupportedCompiler ->
unsupportedCompiler?.groupId?.let { createWarningAboutNonCompatibleCompiler(it) }
})
}
}
}
}

49
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<GradlePropertySnapshotService.Parameters>() {
interface Parameters : BuildServiceParameters {
val gradlePropertiesCacheKindSnapshot: MapProperty<String, String>
val localPropertiesCacheKindSnapshot: MapProperty<String, String>
}
internal val gradleProperties: Map<String, String> = parameters.gradlePropertiesCacheKindSnapshot.get()
internal val localProperties: Map<String, String> = parameters.localPropertiesCacheKindSnapshot.get()
companion object {
fun init(project: Project) {
registerServiceIfAbsent<GradlePropertySnapshotService, Parameters>(project) {
initParams(project)
}
}
fun getInstance(project: Project): GradlePropertySnapshotService =
getExistingServiceRegistration<GradlePropertySnapshotService, Parameters>(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)
}
}
}
}
}

7
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/utils/KGPPropertyProvider.kt

@ -6,8 +6,7 @@
package org.jetbrains.compose.internal.utils package org.jetbrains.compose.internal.utils
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.compose.ComposeMultiplatformBuildService import org.jetbrains.compose.internal.service.GradlePropertySnapshotService
import java.util.*
/** /**
* Reads Kotlin Gradle plugin properties. * Reads Kotlin Gradle plugin properties.
@ -25,13 +24,13 @@ internal abstract class KGPPropertyProvider {
class GradleProperties(private val project: Project) : KGPPropertyProvider() { class GradleProperties(private val project: Project) : KGPPropertyProvider() {
override fun valueOrNull(propertyName: String): String? = override fun valueOrNull(propertyName: String): String? =
ComposeMultiplatformBuildService.getInstance(project).gradlePropertiesSnapshot[propertyName] GradlePropertySnapshotService.getInstance(project).gradleProperties[propertyName]
override val location: String = "gradle.properties" override val location: String = "gradle.properties"
} }
class LocalProperties(private val project: Project) : KGPPropertyProvider() { class LocalProperties(private val project: Project) : KGPPropertyProvider() {
override fun valueOrNull(propertyName: String): String? = override fun valueOrNull(propertyName: String): String? =
ComposeMultiplatformBuildService.getInstance(project).localPropertiesSnapshot[propertyName] GradlePropertySnapshotService.getInstance(project).localProperties[propertyName]
override val location: String = "local.properties" override val location: String = "local.properties"
} }
} }

7
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) { fun testKotlinVersion(kotlinVersion: String) {
val args = arrayOf("build", "--dry-run") val args = arrayOf("help")
val commonPartOfWarning = "Compose Multiplatform Gradle plugin manages this property automatically" val commonPartOfWarning = "Compose Multiplatform Gradle plugin manages this property automatically"
withNativeCacheKindWarningProject(kotlinVersion = kotlinVersion) { withNativeCacheKindWarningProject(kotlinVersion = kotlinVersion) {
gradle(*args).checks { gradle(*args).checks {
@ -162,6 +162,11 @@ class GradlePluginTest : GradlePluginTestBase() {
check.logContainsOnce("Warning: 'kotlin.native.cacheKind' is explicitly set to 'none'") check.logContainsOnce("Warning: 'kotlin.native.cacheKind' is explicitly set to 'none'")
check.logContainsOnce(commonPartOfWarning) 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) { withNativeCacheKindWarningProject(kotlinVersion = kotlinVersion) {
gradle(*args, "-Pkotlin.native.cacheKind=static").checks { gradle(*args, "-Pkotlin.native.cacheKind=static").checks {

58
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 androidxComposeCompilerGroupId = "androidx.compose.compiler"
private val androidxComposeCompilerPlugin = "$androidxComposeCompilerGroupId:compiler:1.4.8" private val androidxComposeCompilerPlugin = "$androidxComposeCompilerGroupId:compiler:1.4.8"
@Suppress("RedundantUnitExpression") private fun testCustomCompilerUnsupportedPlatformsWarning(
@Test platforms: String,
fun testKotlinJs_shows_warning_for_androidx_compose_compiler() = testProject( warningIsExpected: Boolean
TestProjects.customCompilerArgs, defaultTestEnvironment.copy( ) {
kotlinVersion = "1.8.22", testProject(
composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"", TestProjects.customCompilerUnsupportedPlatformsWarning, defaultTestEnvironment.copy(
composeCompilerArgs = "\"suppressKotlinVersionCompatibilityCheck=1.8.22\"" kotlinVersion = "1.8.22",
) composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"",
).let { )
it.gradle(":compileKotlinJs").checks { ).apply {
check.taskSuccessful(":compileKotlinJs") // repeat twice to check that configuration cache hit does not affect the result
check.logContains(createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId)) repeat(2) {
gradle("-Pplatforms=$platforms").checks {
val warning = createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId)
if (warningIsExpected) {
check.logContainsOnce(warning)
} else {
check.logDoesntContain(warning)
}
}
}
} }
Unit
} }
@Suppress("RedundantUnitExpression")
@Test @Test
fun testKotlinJvm_doesnt_show_warning_for_androidx_compose_compiler() = testProject( fun testJs() {
TestProjects.customCompiler, defaultTestEnvironment.copy( testCustomCompilerUnsupportedPlatformsWarning("js", warningIsExpected = true)
kotlinVersion = "1.8.22", }
composeCompilerPlugin = "\"$androidxComposeCompilerPlugin\"",
) @Test
).let { fun testIos() {
it.gradle(":run").checks { testCustomCompilerUnsupportedPlatformsWarning("ios", warningIsExpected = true)
check.taskSuccessful(":run") }
check.logDoesntContain(createWarningAboutNonCompatibleCompiler(androidxComposeCompilerGroupId))
} @Test
Unit fun testJvm() {
testCustomCompilerUnsupportedPlatformsWarning("jvm", warningIsExpected = false)
} }
} }

1
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 proguard = "application/proguard"
const val customCompiler = "application/custom-compiler" const val customCompiler = "application/custom-compiler"
const val customCompilerArgs = "application/custom-compiler-args" const val customCompilerArgs = "application/custom-compiler-args"
const val customCompilerUnsupportedPlatformsWarning = "application/customCompilerUnsupportedPlatformsWarning"
const val jvmKotlinDsl = "application/jvmKotlinDsl" const val jvmKotlinDsl = "application/jvmKotlinDsl"
const val moduleClashCli = "application/moduleClashCli" const val moduleClashCli = "application/moduleClashCli"
const val javaLogger = "application/javaLogger" const val javaLogger = "application/javaLogger"

39
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)
}

2
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

11
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"

19
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)
}
}
}
Loading…
Cancel
Save