Browse Source

Add a warning for a user who sets `compose.kotlinCompilerPlugin` to `androidx.compose.compiler.compiler` (#3313)

* Add a warning for a user who sets `compose.kotlinCompilerPlugin` to `android.compose.compiler.compiler`

The warning will show up only when a multiplatform project contains at least one of the non-jvm targets: k/js, k/native or k/wasm

* fix typo

* PR review

* Refactor to BuildService

* fix typo
pull/3325/head
Oleksandr Karpovich 1 year ago committed by GitHub
parent
commit
f563664166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt
  2. 62
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeMultiplatformBuildService.kt
  3. 44
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt
  4. 6
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt

45
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeCompilerKotlinSupportPlugin.kt

@ -7,12 +7,18 @@ 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.webExt
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import javax.inject.Inject
class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
@Suppress("UnstableApiUsage")
class ComposeCompilerKotlinSupportPlugin @Inject constructor(
@Suppress("UnstableApiUsage") private val buildEventsListenerRegistry: BuildEventsListenerRegistry
) : KotlinCompilerPluginSupportPlugin {
private lateinit var composeCompilerArtifactProvider: ComposeCompilerArtifactProvider
override fun apply(target: Project) {
@ -24,7 +30,33 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
composeExt.kotlinCompilerPlugin.orNull ?:
ComposeCompilerCompatibility.compilerVersionFor(target.getKotlinPluginVersion())
}
collectUnsupportedCompilerPluginUsages(target)
}
}
private fun collectUnsupportedCompilerPluginUsages(target: Project) {
fun Project.hasNonJvmTargets(): Boolean {
val nonJvmTargets = setOf(KotlinPlatformType.native, KotlinPlatformType.js, KotlinPlatformType.wasm)
return mppExtOrNull?.targets?.any {
it.platformType in nonJvmTargets
} ?: false
}
fun SubpluginArtifact.isNonJBComposeCompiler(): Boolean {
return !groupId.startsWith("org.jetbrains.compose.compiler")
}
val service = ComposeMultiplatformBuildService.provider(target)
buildEventsListenerRegistry.onTaskCompletion(service)
service.get().parameters.unsupportedCompilerPlugins.add(
target.provider {
composeCompilerArtifactProvider.compilerArtifact.takeIf {
target.hasNonJvmTargets() && it.isNonJBComposeCompiler()
}
}
)
}
override fun getCompilerPluginId(): String =
@ -69,3 +101,14 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
private fun options(vararg options: Pair<String, String>): List<SubpluginOption> =
options.map { SubpluginOption(it.first, it.second) }
}
private const val COMPOSE_COMPILER_COMPATIBILITY_LINK =
"https://github.com/JetBrains/compose-jb/blob/master/VERSIONING.md#using-compose-multiplatform-compiler"
internal fun createWarningAboutNonCompatibleCompiler(currentCompilerPluginGroupId: String): String {
return """
WARNING: Usage of the Custom Compose Compiler plugin ('$currentCompilerPluginGroupId')
with non-JVM targets (Kotlin/Native, Kotlin/JS, Kotlin/WASM) is not supported.
For more information, please visit: $COMPOSE_COMPILER_COMPATIBILITY_LINK
""".trimMargin()
}

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

@ -0,0 +1,62 @@
package org.jetbrains.compose
import org.gradle.api.Project
import org.gradle.api.logging.Logging
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.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 {
interface Parameters : BuildServiceParameters {
val unsupportedCompilerPlugins: SetProperty<Provider<SubpluginArtifact?>>
}
private val log = Logging.getLogger(this.javaClass)
override fun close() {
notifyAboutUnsupportedCompilerPlugin()
}
private fun notifyAboutUnsupportedCompilerPlugin() {
val unsupportedCompilerPlugin = parameters.unsupportedCompilerPlugins.orNull
?.firstOrNull()
?.orNull
if (unsupportedCompilerPlugin != null) {
log.error(createWarningAboutNonCompatibleCompiler(unsupportedCompilerPlugin.groupId))
}
}
override fun onFinish(event: FinishEvent) {}
companion object {
fun configure(project: Project, fn: Parameters.() -> Unit): Provider<ComposeMultiplatformBuildService> =
project.gradle.sharedServices.registerOrConfigure<Parameters, ComposeMultiplatformBuildService> {
fn()
}
fun provider(project: Project): Provider<ComposeMultiplatformBuildService> = configure(project) {}
}
}
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()
}
}

44
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/UnsupportedCompilerPluginWarningTest.kt

@ -0,0 +1,44 @@
package org.jetbrains.compose.test.tests.integration
import org.jetbrains.compose.createWarningAboutNonCompatibleCompiler
import org.jetbrains.compose.test.utils.GradlePluginTestBase
import org.jetbrains.compose.test.utils.TestProjects
import org.jetbrains.compose.test.utils.checks
import org.junit.jupiter.api.Test
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))
}
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
}
}

6
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/assertUtils.kt

@ -35,6 +35,12 @@ internal class BuildResultChecks(private val result: BuildResult) {
}
}
fun logDoesntContain(substring: String) {
if (result.output.contains(substring)) {
throw AssertionError("Test output contains the unexpected string: '$substring'")
}
}
fun taskSuccessful(task: String) {
taskOutcome(task, TaskOutcome.SUCCESS)
}

Loading…
Cancel
Save