Skip to content

Commit

Permalink
Improve DSL for setting a custom Compose Plugin (#2527)
Browse files Browse the repository at this point in the history
* Improve DSL for setting a custom Compose Plugin

Fixes #2459

Readme: #2526

1. Add `dependencies: Dependencies` extension that is accessible in `compose { }` block
2. Add `Dependencies.compiler` property that can return versions of Compose compiler used by the plugin:
```
compose {
    kotlinCompilerPlugin.set(dependencies.compiler.forKotlin("1.7.20"))
    //kotlinCompilerPlugin.set(dependencies.compiler.auto) // determined by applied version of Kotlin. It is a default.
}
```

3. Add ability to set arguments for Compose Compiler. Now we can write:
```
compose {
    kotlinCompilerPlugin.set(dependencies.compiler.forKotlin("1.7.20"))
    kotlinCompilerPluginArgs.add("suppressKotlinVersionCompatibilityCheck=1.7.21")
}
```

4. Remove checks for different targets

We had a separate check for JS, when we released 1.2.0. It doesn't support Kotlin 1.7.20 at that moment.

It is hard to refactor this feature in the new code, so I removed it. It is not needed now and it had an ugly code. When we will need it again, we'll write it again.

5. Remove the `compose.tests.androidx.compiler.version` property from gradle.properties and remove `defaultAndroidxCompilerEnvironment`

Because they are used only in one test, and it seems there is no reason to use it in another place in the future

* Discussions
  • Loading branch information
igordmn authored Dec 13, 2022
1 parent 15aa81c commit bf958eb
Show file tree
Hide file tree
Showing 21 changed files with 255 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -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()
)
Original file line number Diff line number Diff line change
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)
return target.project.provider {
platformPluginOptions[target.platformType] ?: emptyList()
}
Expand Down
Original file line number Diff line number Diff line change
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)
}
Original file line number Diff line number Diff line change
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).configureEach {
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ abstract class GradlePluginTestBase {
val defaultTestEnvironment: TestEnvironment
get() = TestEnvironment(workingDir = testWorkDir)

val defaultAndroidxCompilerEnvironment: TestEnvironment
get() = defaultTestEnvironment.copy(
kotlinVersion = TestKotlinVersions.AndroidxCompatible,
composeCompilerArtifact = "androidx.compose.compiler:compiler:${TestProperties.androidxCompilerVersion}"
)

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

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

0 comments on commit bf958eb

Please sign in to comment.