Skip to content

Commit

Permalink
Support custom version or coordinates for Compose compiler (#207)
Browse files Browse the repository at this point in the history
Since this project does not build a Kotlin compiler plugin, we should not be forced to do a release for new versions of Kotlin. This property will allow consumers to upgrade their Kotlin version by specifying a newer JetBrains Compose compiler version, or a totally different set of Maven coordinates for a custom Compose compiler artifact.
  • Loading branch information
JakeWharton authored Jul 29, 2023
1 parent ac346eb commit c82b717
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 47 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl

kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"

androidx-compose-compiler = "androidx.compose.compiler:compiler:1.5.1"

compose-compiler = "org.jetbrains.compose.compiler:compiler:1.5.0"
compose-runtime = "org.jetbrains.compose.runtime:runtime:1.4.3"

Expand Down
2 changes: 0 additions & 2 deletions mosaic-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ buildConfig {
}

packageName('com.jakewharton.mosaic.gradle')
buildConfigField("String", "composeCompilerGroupId", "\"${libs.compose.compiler.get().module.group}\"")
buildConfigField("String", "composeCompilerArtifactId", "\"${libs.compose.compiler.get().module.name}\"")
buildConfigField("String", "composeCompilerVersion", "\"${libs.compose.compiler.get().version}\"")
buildConfigField("String", "mosaicVersion", "\"${project.version}\"")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.jakewharton.mosaic.gradle

import org.gradle.api.provider.Property

interface MosaicExtension {
/**
* The version of the JetBrains Compose compiler to use, or a Maven coordinate triple of
* the custom Compose compiler to use.
*
* Example: using a custom version of the JetBrains Compose compiler
* ```kotlin
* redwood {
* kotlinCompilerPlugin.set("1.4.8")
* }
* ```
*
* Example: using a custom Maven coordinate for the Compose compiler
* ```kotlin
* redwood {
* kotlinCompilerPlugin.set("com.example:custom-compose-compiler:1.0.0")
* }
* ```
*/
val kotlinCompilerPlugin: Property<String>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.jakewharton.mosaic.gradle

import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.JavaPlugin.API_CONFIGURATION_NAME
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
Expand All @@ -12,33 +15,28 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption

class MosaicPlugin : KotlinCompilerPluginSupportPlugin {
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>) = true

override fun getCompilerPluginId() = "com.jakewharton.mosaic"

override fun getPluginArtifact() = SubpluginArtifact(
composeCompilerGroupId,
composeCompilerArtifactId,
composeCompilerVersion,
)
private open class MosaicExtensionImpl
@Inject constructor(objectFactory: ObjectFactory) : MosaicExtension {
override val kotlinCompilerPlugin: Property<String> =
objectFactory.property(String::class.java)
.convention(composeCompilerVersion)
}

override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
if (kotlinCompilation.target.platformType == KotlinPlatformType.js) {
// This enables a workaround for Compose lambda generation to function correctly in JS.
// Note: We cannot use SubpluginOption to do this because it targets the Compose plugin.
kotlinCompilation.compilerOptions.options.freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:generateDecoys=true",
)
}
private const val extensionName = "mosaic"

return kotlinCompilation.target.project.provider { emptyList() }
}
@Suppress("unused") // Used reflectively by Gradle.
class MosaicPlugin : KotlinCompilerPluginSupportPlugin {
private lateinit var extension: MosaicExtension

override fun apply(target: Project) {
super.apply(target)

extension = target.extensions.create(
MosaicExtension::class.java,
extensionName,
MosaicExtensionImpl::class.java,
)

if (target.isInternal() && target.path == ":mosaic-runtime") {
// Being lazy and using our own plugin to configure the Compose compiler on our runtime.
// Bail out because otherwise we create a circular dependency reference on ourselves!
Expand Down Expand Up @@ -69,6 +67,39 @@ class MosaicPlugin : KotlinCompilerPluginSupportPlugin {
}
}

override fun isApplicable(kotlinCompilation: KotlinCompilation<*>) = true

override fun getCompilerPluginId() = "com.jakewharton.mosaic"

override fun getPluginArtifact(): SubpluginArtifact {
val plugin = extension.kotlinCompilerPlugin.get()
val parts = plugin.split(":")
return when (parts.size) {
1 -> SubpluginArtifact("org.jetbrains.compose.compiler", "compiler", parts[0])
3 -> SubpluginArtifact(parts[0], parts[1], parts[2])
else -> error(
"""
|Illegal format of '$extensionName.${MosaicExtension::kotlinCompilerPlugin.name}' property.
|Expected format: either '<VERSION>' or '<GROUP_ID>:<ARTIFACT_ID>:<VERSION>'
|Actual value: '$plugin'
""".trimMargin(),
)
}
}

override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
if (kotlinCompilation.target.platformType == KotlinPlatformType.js) {
// This enables a workaround for Compose lambda generation to function correctly in JS.
// Note: We cannot use SubpluginOption to do this because it targets the Compose plugin.
kotlinCompilation.compilerOptions.options.freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:generateDecoys=true",
)
}

return kotlinCompilation.target.project.provider { emptyList() }
}

private fun Project.isInternal(): Boolean {
return properties["com.jakewharton.mosaic.internal"].toString() == "true"
}
Expand Down
2 changes: 1 addition & 1 deletion mosaic-gradle-plugin/src/test/fixture/counter/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
buildscript {
dependencies {
classpath libs.kotlin.gradlePlugin
classpath "com.jakewharton.mosaic:mosaic-gradle-plugin:${mosaicVersion}"
classpath "com.jakewharton.mosaic:mosaic-gradle-plugin:$mosaicVersion"
}
repositories {
maven {
Expand Down
7 changes: 7 additions & 0 deletions mosaic-gradle-plugin/src/test/fixture/counter/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files('../../../../../gradle/libs.versions.toml'))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
buildscript {
dependencies {
classpath "com.jakewharton.mosaic:mosaic-gradle-plugin:$mosaicVersion"
classpath libs.kotlin.gradlePlugin
}

repositories {
maven {
url "file://${projectDir.absolutePath}/../../../../../build/testMaven"
}
mavenCentral()
}
}

apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'com.jakewharton.mosaic'

mosaic {
// Use the AndroidX Compose compiler instead of JetBrains Compose compiler.
kotlinCompilerPlugin = libs.androidx.compose.compiler.get().toString()
}

repositories {
maven {
url "file://${projectDir.absolutePath}/../../../../../build/testMaven"
}
mavenCentral()
google()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files('../../../../../gradle/libs.versions.toml'))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
buildscript {
dependencies {
classpath "com.jakewharton.mosaic:mosaic-gradle-plugin:$mosaicVersion"
classpath libs.kotlin.gradlePlugin
}

repositories {
maven {
url "file://${projectDir.absolutePath}/../../../../../build/testMaven"
}
mavenCentral()
google()
}
}

apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'com.jakewharton.mosaic'

mosaic {
kotlinCompilerPlugin = 'wrong:format'
}

repositories {
maven {
url "file://${projectDir.absolutePath}/../../../../../build/testMaven"
}
mavenCentral()
google()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files('../../../../../gradle/libs.versions.toml'))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
buildscript {
ext.kotlinVersion = '1.8.20'

dependencies {
classpath "com.jakewharton.mosaic:mosaic-gradle-plugin:$mosaicVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}

repositories {
maven {
url "file://${projectDir.absolutePath}/../../../../../build/testMaven"
}
mavenCentral()
google()
}
}

if (kotlinVersion == libs.kotlin.gradlePlugin.get().version) {
throw RuntimeException("This test requires a different version of Kotlin then the Mosaic build")
}

apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'com.jakewharton.mosaic'

mosaic {
// Use the JetBrains Compose compiler version for the version of Kotlin used by this project.
kotlinCompilerPlugin = '1.4.8'
}

repositories {
maven {
url "file://${projectDir.absolutePath}/../../../../../build/testMaven"
}
mavenCentral()
google()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
from(files('../../../../../gradle/libs.versions.toml'))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
package com.jakewharton.mosaic.gradle

import com.google.common.truth.Truth.assertThat
import java.io.File
import org.gradle.testkit.runner.GradleRunner
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import java.io.File

@RunWith(Parameterized::class)
class FixtureTest(
private val fixtureName: String,
) {
companion object {
@JvmStatic
@Parameters(name = "{0}")
fun parameters() = listOf(
arrayOf("counter"),
class FixtureTest {
@Test fun counter() {
fixture("counter").build()
}

@Test fun customCompilerCoordinates() {
fixture("custom-compiler-coordinates").build()
}

@Test fun customCompilerInvalid() {
val result = fixture("custom-compiler-invalid").buildAndFail()
assertThat(result.output).contains(
"""
|Illegal format of 'mosaic.kotlinCompilerPlugin' property.
|Expected format: either '<VERSION>' or '<GROUP_ID>:<ARTIFACT_ID>:<VERSION>'
|Actual value: 'wrong:format'
""".trimMargin(),
)
}

private val fixturesDir = File("src/test/fixture")
private fun versionProperty() = "-PmosaicVersion=$mosaicVersion"
@Test fun customCompilerVersion() {
fixture("custom-compiler-version").build()
}

@Test fun todo() {
val fixtureDir = File(fixturesDir, fixtureName)
val gradleRoot = File(fixtureDir, "gradle").also { it.mkdir() }
File("../gradle").copyRecursively(File(gradleRoot.path), true)
private fun fixture(
name: String,
tasks: Array<String> = arrayOf("clean", "build"),
): GradleRunner {
val fixtureDir = File("src/test/fixture", name)
val gradleWrapper = File(fixtureDir, "gradle/wrapper").also { it.mkdirs() }
File("../gradle/wrapper").copyRecursively(File(gradleWrapper.path), true)

GradleRunner.create()
return GradleRunner.create()
.withProjectDir(fixtureDir)
.withArguments("clean", "build", "--stacktrace", versionProperty())
.withDebug(true) // Run in-process for speed.
.build()
.withArguments(*tasks, "--stacktrace", "-PmosaicVersion=$mosaicVersion")
.withDebug(true)
}
}

0 comments on commit c82b717

Please sign in to comment.