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 5c025cedb0..66d64edaf6 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 @@ -8,11 +8,58 @@ package org.jetbrains.compose import org.gradle.api.Project import org.gradle.api.provider.Provider import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider +import org.jetbrains.compose.internal.KOTLIN_JVM_PLUGIN_ID +import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID +import org.jetbrains.compose.internal.Version +import org.jetbrains.compose.internal.ideaIsInSyncProvider 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.plugin.KotlinBasePlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +internal fun Project.configureComposeCompilerPlugin() { + plugins.withId(KOTLIN_MPP_PLUGIN_ID) { plugin -> + configureComposeCompilerPlugin(plugin as KotlinBasePlugin) + } + plugins.withId(KOTLIN_JVM_PLUGIN_ID) { plugin -> + configureComposeCompilerPlugin(plugin as KotlinBasePlugin) + } +} + +internal const val newCompilerIsAvailableVersion = "2.0.0-RC2" +internal const val newComposeCompilerKotlinSupportPluginId = "org.jetbrains.kotlin.plugin.compose" +internal const val newComposeCompilerError = + "Since Kotlin $newCompilerIsAvailableVersion to use Compose Multiplatform " + + "you must apply \"$newComposeCompilerKotlinSupportPluginId\" plugin." + + "\nSee the migration guide https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compiler/compose-compiler.html#migrating-a-compose-multiplatform-project" + +private fun Project.configureComposeCompilerPlugin(kgp: KotlinBasePlugin) { + val kgpVersion = kgp.pluginVersion + + if (Version.fromString(kgpVersion) < Version.fromString(newCompilerIsAvailableVersion)) { + logger.info("Apply ComposeCompilerKotlinSupportPlugin (KGP version = $kgpVersion)") + project.plugins.apply(ComposeCompilerKotlinSupportPlugin::class.java) + } else { + //There is no other way to check that the plugin WASN'T applied! + afterEvaluate { + logger.info("Check that new '$newComposeCompilerKotlinSupportPluginId' was applied") + if (!project.plugins.hasPlugin(newComposeCompilerKotlinSupportPluginId)) { + val ideaIsInSync = project.ideaIsInSyncProvider().get() + if (ideaIsInSync) logger.error("e: Configuration problem: $newComposeCompilerError") + else error("e: Configuration problem: $newComposeCompilerError") + } + } + } +} class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin { private lateinit var composeCompilerArtifactProvider: ComposeCompilerArtifactProvider diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeExtension.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeExtension.kt index ee3c0a52cf..23287fcf30 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeExtension.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposeExtension.kt @@ -31,6 +31,7 @@ abstract class ComposeExtension @Inject constructor( * ``` * (see available versions here: https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility) */ + @Deprecated("Since Kotlin $newCompilerIsAvailableVersion Compose Compiler configuration is moved to the \"$newComposeCompilerKotlinSupportPluginId\" plugin") val kotlinCompilerPlugin: Property = objects.nullableProperty() /** @@ -41,6 +42,7 @@ abstract class ComposeExtension @Inject constructor( * See all available arguments here: * https://github.com/androidx/androidx/blob/androidx-main/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt */ + @Deprecated("Since Kotlin $newCompilerIsAvailableVersion Compose Compiler configuration is moved to the \"$newComposeCompilerKotlinSupportPluginId\" plugin") val kotlinCompilerPluginArgs: ListProperty = objects.listProperty(String::class.java) /** @@ -51,6 +53,7 @@ abstract class ComposeExtension @Inject constructor( * platformTypes.set(platformTypes.get() - KotlinPlatformType.native) * ``` */ + @Deprecated("Since Kotlin $newCompilerIsAvailableVersion Compose Compiler configuration is moved to the \"$newComposeCompilerKotlinSupportPluginId\" plugin") val platformTypes: SetProperty = objects.setProperty(KotlinPlatformType::class.java).apply { set(KotlinPlatformType.values().toMutableSet()) } 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 4457bdd56b..cc7fa0a122 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 @@ -27,6 +27,7 @@ import org.jetbrains.compose.internal.utils.currentTarget import org.jetbrains.compose.resources.ResourcesExtension import org.jetbrains.compose.resources.configureComposeResources import org.jetbrains.compose.web.WebExtension +import org.jetbrains.kotlin.com.github.gundy.semver4j.SemVer import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile import org.jetbrains.kotlin.gradle.plugin.* @@ -57,7 +58,7 @@ abstract class ComposePlugin : Plugin { project.initializePreview(desktopExtension) composeExtension.extensions.create("web", WebExtension::class.java) - project.plugins.apply(ComposeCompilerKotlinSupportPlugin::class.java) + project.configureComposeCompilerPlugin() project.configureNativeCompilerCaching() project.configureComposeResources(resourcesExtension) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/IdeaImportTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/IdeaImportTask.kt new file mode 100644 index 0000000000..3f40444ad4 --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/IdeaImportTask.kt @@ -0,0 +1,32 @@ +package org.jetbrains.compose.internal + +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +internal fun Project.ideaIsInSyncProvider(): Provider = provider { + System.getProperty("idea.sync.active", "false").toBoolean() +} + +/** + * This task should be FAST and SAFE! Because it is being run during IDE import. + */ +internal abstract class IdeaImportTask : DefaultTask() { + @get:Input + val ideaIsInSync: Provider = project.ideaIsInSyncProvider() + + @TaskAction + fun run() { + try { + safeAction() + } catch (e: Exception) { + //message must contain two ':' symbols to be parsed by IDE UI! + logger.error("e: $name task was failed:", e) + if (!ideaIsInSync.get()) throw e + } + } + + abstract fun safeAction() +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/Version.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/Version.kt new file mode 100644 index 0000000000..887de3095f --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/Version.kt @@ -0,0 +1,31 @@ +package org.jetbrains.compose.internal + +internal data class Version( + val major: Int, + val minor: Int, + val patch: Int, + val meta: String +): Comparable { + override fun compareTo(other: Version): Int = when { + major != other.major -> major - other.major + minor != other.minor -> minor - other.minor + patch != other.patch -> patch - other.patch + else -> { + if (meta.isEmpty()) 1 + else if (other.meta.isEmpty()) -1 + else meta.compareTo(other.meta) + } + } + + companion object { + private val SEMVER_REGEXP = """^(\d+)(?:\.(\d*))?(?:\.(\d*))?(?:-(.*))?${'$'}""".toRegex() + fun fromString(versionString: String): Version { + val matchResult: MatchResult = SEMVER_REGEXP.matchEntire(versionString) ?: return Version(0,0,0, "") + val major: Int = matchResult.groups[1]?.value?.toInt() ?: 0 + val minor: Int = matchResult.groups[2]?.value?.toInt() ?: 0 + val patch: Int = matchResult.groups[3]?.value?.toInt() ?: 0 + val meta: String = matchResult.groups[4]?.value ?: "" + return Version(major, minor, patch, meta) + } + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt index 57f4cb0ab4..50acdecdcf 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/internal/constants.kt @@ -9,3 +9,5 @@ internal const val KOTLIN_MPP_PLUGIN_ID = "org.jetbrains.kotlin.multiplatform" internal const val KOTLIN_JVM_PLUGIN_ID = "org.jetbrains.kotlin.jvm" internal const val KOTLIN_JS_PLUGIN_ID = "org.jetbrains.kotlin.js" internal const val COMPOSE_PLUGIN_ID = "org.jetbrains.compose" + +internal const val IDEA_IMPORT_TASK_NAME = "prepareKotlinIdeaImport" \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt index 08dae761b7..12f8486fb5 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt @@ -13,6 +13,8 @@ import org.jetbrains.compose.internal.KOTLIN_JVM_PLUGIN_ID import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.extraProperties import java.io.File @@ -29,11 +31,11 @@ private val androidPluginIds = listOf( internal fun Project.configureComposeResources(extension: ResourcesExtension) { val config = provider { extension } - plugins.withId(KOTLIN_MPP_PLUGIN_ID) { onKgpApplied(config) } + plugins.withId(KOTLIN_MPP_PLUGIN_ID) { onKgpApplied(config, it as KotlinBasePlugin) } plugins.withId(KOTLIN_JVM_PLUGIN_ID) { onKotlinJvmApplied(config) } } -private fun Project.onKgpApplied(config: Provider) { +private fun Project.onKgpApplied(config: Provider, kgp: KotlinBasePlugin) { val kotlinExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java) val hasKmpResources = extraProperties.has(KMP_RES_EXT) @@ -47,7 +49,7 @@ private fun Project.onKgpApplied(config: Provider) { if (!hasKmpResources) logger.info( """ Compose resources publication requires Kotlin Gradle Plugin >= 2.0 - Current Kotlin Gradle Plugin is ${kotlinExtension.coreLibrariesVersion} + Current Kotlin Gradle Plugin is ${kgp.pluginVersion} """.trimIndent() ) if (currentGradleVersion < minGradleVersion) logger.info( diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt index 3304eea4a7..8c28fa4f04 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResourcesGeneration.kt @@ -3,6 +3,8 @@ package org.jetbrains.compose.resources import org.gradle.api.Project import org.gradle.api.provider.Provider import org.jetbrains.compose.ComposePlugin +import org.jetbrains.compose.internal.IDEA_IMPORT_TASK_NAME +import org.jetbrains.compose.internal.IdeaImportTask import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet @@ -67,7 +69,7 @@ internal fun Project.configureComposeResourcesGeneration( //setup task execution during IDE import tasks.configureEach { importTask -> - if (importTask.name == "prepareKotlinIdeaImport") { + if (importTask.name == IDEA_IMPORT_TASK_NAME) { importTask.dependsOn(tasks.withType(IdeaImportTask::class.java)) } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt index bc4155b4e0..5ce79a5268 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt @@ -1,38 +1,13 @@ package org.jetbrains.compose.resources -import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction +import org.jetbrains.compose.internal.IdeaImportTask import java.io.File -/** - * This task should be FAST and SAFE! Because it is being run during IDE import. - */ -internal abstract class IdeaImportTask : DefaultTask() { - @get:Input - val ideaIsInSync: Provider = project.provider { - System.getProperty("idea.sync.active", "false").toBoolean() - } - - @TaskAction - fun run() { - try { - safeAction() - } catch (e: Exception) { - //message must contain two ':' symbols to be parsed by IDE UI! - logger.error("e: $name task was failed:", e) - if (!ideaIsInSync.get()) throw e - } - } - - abstract fun safeAction() -} - internal abstract class GenerateResClassTask : IdeaImportTask() { companion object { private const val RES_FILE_NAME = "Res" diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt index 18940b68eb..9aca01b3ff 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResourceAccessorsTask.kt @@ -10,6 +10,7 @@ import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction +import org.jetbrains.compose.internal.IdeaImportTask import java.io.File import java.nio.file.Path import kotlin.io.path.relativeTo diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt index 07cdc50093..11131350fe 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/PrepareComposeResources.kt @@ -14,6 +14,7 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFiles import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskProvider +import org.jetbrains.compose.internal.IdeaImportTask import org.jetbrains.compose.internal.utils.uppercaseFirstChar import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.w3c.dom.Node diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt index 1e4fbfdcec..2f0b21921e 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/KotlinCompatibilityTest.kt @@ -5,6 +5,8 @@ package org.jetbrains.compose.test.tests.integration +import org.jetbrains.compose.newCompilerIsAvailableVersion +import org.jetbrains.compose.newComposeCompilerError import org.jetbrains.compose.test.utils.GradlePluginTestBase import org.jetbrains.compose.test.utils.TestProjects import org.jetbrains.compose.test.utils.checks @@ -46,4 +48,16 @@ class KotlinCompatibilityTest : GradlePluginTestBase() { check.taskSuccessful(":compileKotlinJs") } } + + /* TODO uncomment the test when Kotlin RC2 will be published + @Test + fun testNewCompilerPluginError() { + val testProject = testProject( + TestProjects.mpp, + testEnvironment = defaultTestEnvironment.copy(kotlinVersion = newCompilerIsAvailableVersion) + ) + testProject.gradleFailure("tasks").checks { + check.logContains(newComposeCompilerError) + } + }*/ } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/SemVerTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/SemVerTest.kt new file mode 100644 index 0000000000..71c5064ebf --- /dev/null +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/unit/SemVerTest.kt @@ -0,0 +1,19 @@ +package org.jetbrains.compose.test.tests.unit + +import org.jetbrains.compose.internal.Version +import kotlin.test.Test + +class SemVerTest { + @Test + fun testSemVersionParser() { + assert(Version.fromString("0") < Version.fromString("1.2.3")) + assert(Version.fromString("2") > Version.fromString("1.2.3")) + assert(Version.fromString("1.1") > Version.fromString("1-abc")) + assert(Version.fromString("1.1") > Version.fromString("1")) + assert(Version.fromString("2.0.0-RC1") > Version.fromString("2.0.0-Beta5")) + assert(Version.fromString("2.0.0-RC2") > Version.fromString("2.0.0-RC1")) + assert(Version.fromString("2.0.0-RC1") > Version.fromString("1.9.23")) + assert(Version.fromString("2.0.0") > Version.fromString("2.0.0-RC1")) + assert(Version.fromString("2.0.0-RC1") == Version.fromString("2.0.0-RC1")) + } +} \ No newline at end of file