Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve DSL for setting a custom Compose Plugin #2527

Merged
merged 2 commits into from Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,16 +1,20 @@
package org.jetbrains.compose

import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
private const val KOTLIN_COMPATABILITY_LINK =
"https://github.com/JetBrains/compose-jb/blob/master/VERSIONING.md#kotlin-compatibility"

internal object ComposeCompilerCompatability {
fun compilerVersionFor(kotlinVersion: String): ComposeCompilerVersion? = when (kotlinVersion) {
"1.7.10" -> ComposeCompilerVersion("1.3.0")
"1.7.20" -> ComposeCompilerVersion("1.3.2.1")
else -> null
private val kotlinToCompiler = sortedMapOf(
"1.7.10" to "1.3.0",
"1.7.20" to "1.3.2.1",
)

fun compilerVersionFor(kotlinVersion: String): String {
return kotlinToCompiler[kotlinVersion] ?: throw RuntimeException(
"This version of Compose Multiplatform doesn't support Kotlin " +
"$kotlinVersion. " +
"Please see $KOTLIN_COMPATABILITY_LINK " +
"to know the latest supported version of Kotlin."
)
}
}

internal data class ComposeCompilerVersion(
val version: String,
val unsupportedPlatforms: Set<KotlinPlatformType> = emptySet()
)
Expand Up @@ -20,10 +20,9 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {
target.plugins.withType(ComposePlugin::class.java) {
val composeExt = target.extensions.getByType(ComposeExtension::class.java)

composeCompilerArtifactProvider = ComposeCompilerArtifactProvider(
kotlinVersion = target.getKotlinPluginVersion()
) {
composeExt.kotlinCompilerPlugin.orNull
composeCompilerArtifactProvider = ComposeCompilerArtifactProvider {
composeExt.kotlinCompilerPlugin.orNull ?:
ComposeCompilerCompatability.compilerVersionFor(target.getKotlinPluginVersion())
}
}
}
Expand Down Expand Up @@ -58,7 +57,6 @@ class ComposeCompilerKotlinSupportPlugin : KotlinCompilerPluginSupportPlugin {

override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
val target = kotlinCompilation.target
composeCompilerArtifactProvider.checkTargetSupported(target)
igordmn marked this conversation as resolved.
Show resolved Hide resolved
return target.project.provider {
platformPluginOptions[target.platformType] ?: emptyList()
}
Expand Down
Expand Up @@ -5,15 +5,41 @@

package org.jetbrains.compose

import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.jetbrains.compose.desktop.application.internal.nullableProperty
import javax.inject.Inject

abstract class ComposeExtension @Inject constructor(
objects: ObjectFactory
objects: ObjectFactory,
project: Project
) : ExtensionAware {
/**
* Custom Compose Compiler maven coordinates. You can set it using values provided
* by [ComposePlugin.CompilerDependencies]:
* ```
* kotlinCompilerPlugin.set(dependencies.compiler.forKotlin("1.7.20"))
* ```
* or set it to the Jetpack Compose Compiler:
* ```
* kotlinCompilerPlugin.set("androidx.compose.compiler:compiler:1.4.0-alpha02")
* ```
* (see available versions here: https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility)
*/
val kotlinCompilerPlugin: Property<String?> = objects.nullableProperty()
}

/**
* List of the arguments applied to the Compose Compiler. Example:
* ```
* kotlinCompilerPluginArgs.add("suppressKotlinVersionCompatibilityCheck=1.7.21")
* ```
* 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
*/
val kotlinCompilerPluginArgs: ListProperty<String> = objects.listProperty(String::class.java)

val dependencies = ComposePlugin.Dependencies(project)
}
Expand Up @@ -19,7 +19,6 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.plugins.ExtensionAware
import org.jetbrains.compose.android.AndroidExtension
import org.jetbrains.compose.desktop.DesktopExtension
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
import org.jetbrains.compose.desktop.application.internal.configureDesktop
import org.jetbrains.compose.desktop.application.internal.currentTarget
import org.jetbrains.compose.desktop.preview.internal.initializePreview
Expand All @@ -28,18 +27,19 @@ import org.jetbrains.compose.experimental.internal.checkExperimentalTargetsWithS
import org.jetbrains.compose.experimental.internal.configureExperimental
import org.jetbrains.compose.web.WebExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

internal val composeVersion get() = ComposeBuildConfig.composeVersion

class ComposePlugin : Plugin<Project> {
override fun apply(project: Project) {
val composeExtension = project.extensions.create("compose", ComposeExtension::class.java)
val composeExtension = project.extensions.create("compose", ComposeExtension::class.java, project)
val desktopExtension = composeExtension.extensions.create("desktop", DesktopExtension::class.java)
val androidExtension = composeExtension.extensions.create("android", AndroidExtension::class.java)
val experimentalExtension = composeExtension.extensions.create("experimental", ExperimentalExtension::class.java)

project.dependencies.extensions.add("compose", Dependencies)
project.dependencies.extensions.add("compose", Dependencies(project))

if (!project.buildFile.endsWith(".gradle.kts")) {
setUpGroovyDslExtensions(project)
Expand Down Expand Up @@ -68,6 +68,15 @@ class ComposePlugin : Plugin<Project> {
it.replacedBy(replacement, "org.jetbrains.compose isn't compatible with androidx.compose, because it is the same library published with different maven coordinates")
}
}

project.tasks.withType(KotlinCompile::class.java) {
igordmn marked this conversation as resolved.
Show resolved Hide resolved
it.kotlinOptions.apply {
freeCompilerArgs = freeCompilerArgs +
composeExtension.kotlinCompilerPluginArgs.get().flatMap { arg ->
listOf("-P", "plugin:androidx.compose.compiler.plugins.kotlin:$arg")
}
}
}
}
}

Expand Down Expand Up @@ -97,8 +106,9 @@ class ComposePlugin : Plugin<Project> {
}
}

object Dependencies {
class Dependencies(project: Project) {
val desktop = DesktopDependencies
val compiler = CompilerDependencies(project)
val animation get() = composeDependency("org.jetbrains.compose.animation:animation")
val animationGraphics get() = composeDependency("org.jetbrains.compose.animation:animation-graphics")
val foundation get() = composeDependency("org.jetbrains.compose.foundation:foundation")
Expand Down Expand Up @@ -130,6 +140,16 @@ class ComposePlugin : Plugin<Project> {
}
}

class CompilerDependencies(private val project: Project) {
fun forKotlin(version: String) = "org.jetbrains.compose.compiler:compiler:" +
ComposeCompilerCompatability.compilerVersionFor(version)

/**
* Compose Compiler that is chosen by the version of Kotlin applied to the Gradle project
*/
val auto get() = forKotlin(project.getKotlinPluginVersion())
}

object DesktopComponentsDependencies {
@ExperimentalComposeLibrary
val splitPane = composeDependency("org.jetbrains.compose.components:components-splitpane")
Expand Down Expand Up @@ -165,7 +185,7 @@ private fun composeDependency(groupWithArtifact: String) = "$groupWithArtifact:$
private fun setUpGroovyDslExtensions(project: Project) {
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
(project.extensions.getByName("kotlin") as? ExtensionAware)?.apply {
extensions.add("compose", ComposePlugin.Dependencies)
extensions.add("compose", ComposePlugin.Dependencies(project))
}
}
(project.repositories as? ExtensionAware)?.extensions?.apply {
Expand Down
Expand Up @@ -5,44 +5,16 @@

package org.jetbrains.compose.internal

import org.jetbrains.compose.ComposeCompilerCompatability
import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider.DefaultCompiler.pluginArtifact
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact

private const val KOTLIN_COMPATABILITY_LINK =
"https://github.com/JetBrains/compose-jb/blob/master/VERSIONING.md#kotlin-compatibility"

internal class ComposeCompilerArtifactProvider(
private val kotlinVersion: String,
private val customPluginString: () -> String?
private val customPluginString: () -> String
) {
fun checkTargetSupported(target: KotlinTarget) {
require(!unsupportedPlatforms.contains(target.platformType)) {
"This version of Compose Multiplatform doesn't support Kotlin " +
"$kotlinVersion for ${target.platformType} target. " +
"Please see $KOTLIN_COMPATABILITY_LINK " +
"to know the latest supported version of Kotlin."
}
}

private val autoCompilerVersion by lazy {
requireNotNull(
ComposeCompilerCompatability.compilerVersionFor(kotlinVersion)
) {
"This version of Compose Multiplatform doesn't support Kotlin " +
"$kotlinVersion. " +
"Please see $KOTLIN_COMPATABILITY_LINK " +
"to know the latest supported version of Kotlin."
}
}

private val customCompilerArtifact: SubpluginArtifact? by lazy {
val compilerArtifact: SubpluginArtifact by lazy {
val customPlugin = customPluginString()
val customCoordinates = customPlugin?.split(":")
when (customCoordinates?.size) {
null -> null
val customCoordinates = customPlugin.split(":")
when (customCoordinates.size) {
1 -> {
val customVersion = customCoordinates[0]
check(customVersion.isNotBlank()) { "'compose.kotlinCompilerPlugin' cannot be blank!" }
Expand All @@ -61,14 +33,6 @@ internal class ComposeCompilerArtifactProvider(
}
}

private val unsupportedPlatforms: Set<KotlinPlatformType> by lazy {
if (customCompilerArtifact != null) emptySet() else autoCompilerVersion.unsupportedPlatforms
}

val compilerArtifact: SubpluginArtifact get() {
return customCompilerArtifact ?: pluginArtifact(version = autoCompilerVersion.version)
}

val compilerHostedArtifact: SubpluginArtifact
get() = compilerArtifact.run {
val newArtifactId =
Expand Down
Expand Up @@ -60,9 +60,45 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
}

/**
* Test the version of Compose Compiler published by Google.
* See https://developer.android.com/jetpack/androidx/releases/compose-kotlin
*/
@Test
fun testAndroidxCompiler() = with(testProject(TestProjects.androidxCompiler, defaultAndroidxCompilerEnvironment)) {
gradle(":runDistributable").build().checks { check ->
fun testAndroidxCompiler() = testProject(
TestProjects.customCompiler, defaultTestEnvironment.copy(
kotlinVersion = "1.6.10",
composeCompilerPlugin = "\"androidx.compose.compiler:compiler:1.1.1\""
)
).checkCustomComposeCompiler()

@Test
fun testSettingLatestCompiler() = testProject(
TestProjects.customCompiler, defaultTestEnvironment.copy(
kotlinVersion = "1.7.20",
composeCompilerPlugin = "dependencies.compiler.forKotlin(\"1.7.20\")",
)
).checkCustomComposeCompiler()

@Test
fun testSettingAutoCompiler() = testProject(
TestProjects.customCompiler, defaultTestEnvironment.copy(
kotlinVersion = "1.7.10",
composeCompilerPlugin = "dependencies.compiler.auto",
)
).checkCustomComposeCompiler()

@Test
fun testKotlinCheckDisabled() = testProject(
TestProjects.customCompilerArgs, defaultTestEnvironment.copy(
kotlinVersion = "1.7.21",
composeCompilerPlugin = "dependencies.compiler.forKotlin(\"1.7.20\")",
composeCompilerArgs = "\"suppressKotlinVersionCompatibilityCheck=1.7.21\""
)
).checkCustomComposeCompiler()

private fun TestProject.checkCustomComposeCompiler() {
gradle(":runDistributable").build().checks {
val actualMainImage = file("main-image.actual.png")
val expectedMainImage = file("main-image.expected.png")
assert(actualMainImage.readBytes().contentEquals(expectedMainImage.readBytes())) {
Expand Down
Expand Up @@ -14,38 +14,19 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*

internal class ComposeCompilerArtifactProviderTest {
@Test
fun defaultCompilerArtifact() {
assertArtifactEquals(
Expected.jbCompiler,
Actual.compiler(null, TestProperties.composeCompilerCompatibleKotlinVersion)
)
}

@Test
fun defaultCompilerHostedArtifact() {
assertArtifactEquals(
Expected.jbCompilerHosted,
Actual.compilerHosted(null, TestProperties.composeCompilerCompatibleKotlinVersion)
)
}

@Test
fun customVersion() {
assertArtifactEquals(
Expected.jbCompiler.copy(version = "10.20.30"),
Actual.compiler("10.20.30", TestProperties.composeCompilerCompatibleKotlinVersion)
Actual.compiler("10.20.30")
)
}

@Test
fun customCompiler() {
assertArtifactEquals(
Expected.googleCompiler.copy(version = "1.3.1"),
Actual.compiler(
"androidx.compose.compiler:compiler:1.3.1",
TestProperties.androidxCompilerCompatibleKotlinVersion
)
Actual.compiler("androidx.compose.compiler:compiler:1.3.1")
)
}

Expand All @@ -54,10 +35,7 @@ internal class ComposeCompilerArtifactProviderTest {
// check that we don't replace artifactId for non-jb compiler
assertArtifactEquals(
Expected.googleCompiler.copy(version = "1.3.1"),
Actual.compilerHosted(
"androidx.compose.compiler:compiler:1.3.1",
TestProperties.composeCompilerCompatibleKotlinVersion
)
Actual.compilerHosted("androidx.compose.compiler:compiler:1.3.1")
)
}

Expand All @@ -68,9 +46,9 @@ internal class ComposeCompilerArtifactProviderTest {
testIllegalCompiler("")
}

private fun testIllegalCompiler(pluginString: String?) {
private fun testIllegalCompiler(pluginString: String) {
try {
Actual.compiler(pluginString, "")
Actual.compiler(pluginString)
} catch (e: Exception) {
return
}
Expand All @@ -79,11 +57,11 @@ internal class ComposeCompilerArtifactProviderTest {
}

object Actual {
fun compiler(pluginString: String?, kotlinVersion: String) =
ComposeCompilerArtifactProvider(kotlinVersion) { pluginString }.compilerArtifact
fun compiler(pluginString: String) =
ComposeCompilerArtifactProvider { pluginString }.compilerArtifact

fun compilerHosted(pluginString: String?, kotlinVersion: String) =
ComposeCompilerArtifactProvider(kotlinVersion) { pluginString }.compilerHostedArtifact
fun compilerHosted(pluginString: String) =
ComposeCompilerArtifactProvider { pluginString }.compilerHostedArtifact
}

object Expected {
Expand Down
Expand Up @@ -15,12 +15,6 @@ abstract class GradlePluginTestBase {
val defaultTestEnvironment: TestEnvironment
get() = TestEnvironment(workingDir = testWorkDir)

val defaultAndroidxCompilerEnvironment: TestEnvironment
igordmn marked this conversation as resolved.
Show resolved Hide resolved
get() = defaultTestEnvironment.copy(
kotlinVersion = TestKotlinVersions.AndroidxCompatible,
composeCompilerArtifact = "androidx.compose.compiler:compiler:${TestProperties.androidxCompilerVersion}"
)

fun testProject(
name: String,
testEnvironment: TestEnvironment = defaultTestEnvironment
Expand Down
Expand Up @@ -7,5 +7,4 @@ package org.jetbrains.compose.test.utils

object TestKotlinVersions {
val Default = TestProperties.composeCompilerCompatibleKotlinVersion
val AndroidxCompatible = TestProperties.androidxCompilerCompatibleKotlinVersion
}