From 4907bbb3c2b6a3e2ee369dd1d24e5c232d1e5d50 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Sat, 29 Jul 2023 00:20:11 -0400 Subject: [PATCH] Support custom version or coordinates for Compose compiler 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 thier Kotlin version by specifying a newer JetBrains Compose compiler version, or a totally different set of Maven coordiantes for a custom Compose compiler aritfact. --- gradle/libs.versions.toml | 2 + mosaic-gradle-plugin/build.gradle | 2 - .../mosaic/gradle/MosaicExtension.kt | 25 +++++++ .../jakewharton/mosaic/gradle/MosaicPlugin.kt | 73 +++++++++++++------ .../src/test/fixture/counter/build.gradle | 2 +- .../src/test/fixture/counter/settings.gradle | 7 ++ .../custom-compiler-coordinates/build.gradle | 29 ++++++++ .../settings.gradle | 7 ++ .../custom-compiler-invalid/build.gradle | 29 ++++++++ .../custom-compiler-invalid/settings.gradle | 7 ++ .../custom-compiler-version/build.gradle | 36 +++++++++ .../custom-compiler-version/settings.gradle | 7 ++ .../jakewharton/mosaic/gradle/FixtureTest.kt | 55 ++++++++------ 13 files changed, 234 insertions(+), 47 deletions(-) create mode 100644 mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicExtension.kt create mode 100644 mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/build.gradle create mode 100644 mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/settings.gradle create mode 100644 mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/build.gradle create mode 100644 mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/settings.gradle create mode 100644 mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/build.gradle create mode 100644 mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/settings.gradle diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0583b8407..3b352462a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/mosaic-gradle-plugin/build.gradle b/mosaic-gradle-plugin/build.gradle index a5e6e2d47..619a557d8 100644 --- a/mosaic-gradle-plugin/build.gradle +++ b/mosaic-gradle-plugin/build.gradle @@ -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}\"") } diff --git a/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicExtension.kt b/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicExtension.kt new file mode 100644 index 000000000..a155b8edf --- /dev/null +++ b/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicExtension.kt @@ -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 +} diff --git a/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicPlugin.kt b/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicPlugin.kt index 91bbd99c8..fea037eff 100644 --- a/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicPlugin.kt +++ b/mosaic-gradle-plugin/src/main/kotlin/com/jakewharton/mosaic/gradle/MosaicPlugin.kt @@ -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 @@ -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 = + objectFactory.property(String::class.java) + .convention(composeCompilerVersion) +} - override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> { - 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! @@ -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 '' or '::' + |Actual value: '$plugin' + """.trimMargin(), + ) + } + } + + override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> { + 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" } diff --git a/mosaic-gradle-plugin/src/test/fixture/counter/build.gradle b/mosaic-gradle-plugin/src/test/fixture/counter/build.gradle index 2a8e6d230..cf5f11bfc 100644 --- a/mosaic-gradle-plugin/src/test/fixture/counter/build.gradle +++ b/mosaic-gradle-plugin/src/test/fixture/counter/build.gradle @@ -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 { diff --git a/mosaic-gradle-plugin/src/test/fixture/counter/settings.gradle b/mosaic-gradle-plugin/src/test/fixture/counter/settings.gradle index e69de29bb..de282febf 100644 --- a/mosaic-gradle-plugin/src/test/fixture/counter/settings.gradle +++ b/mosaic-gradle-plugin/src/test/fixture/counter/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../../../../gradle/libs.versions.toml')) + } + } +} diff --git a/mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/build.gradle b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/build.gradle new file mode 100644 index 000000000..349f00990 --- /dev/null +++ b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/build.gradle @@ -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() +} diff --git a/mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/settings.gradle b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/settings.gradle new file mode 100644 index 000000000..de282febf --- /dev/null +++ b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-coordinates/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../../../../gradle/libs.versions.toml')) + } + } +} diff --git a/mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/build.gradle b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/build.gradle new file mode 100644 index 000000000..65d421ba4 --- /dev/null +++ b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/build.gradle @@ -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() +} diff --git a/mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/settings.gradle b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/settings.gradle new file mode 100644 index 000000000..de282febf --- /dev/null +++ b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-invalid/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../../../../gradle/libs.versions.toml')) + } + } +} diff --git a/mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/build.gradle b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/build.gradle new file mode 100644 index 000000000..3633c977b --- /dev/null +++ b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/build.gradle @@ -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() +} diff --git a/mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/settings.gradle b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/settings.gradle new file mode 100644 index 000000000..de282febf --- /dev/null +++ b/mosaic-gradle-plugin/src/test/fixture/custom-compiler-version/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../../../../gradle/libs.versions.toml')) + } + } +} diff --git a/mosaic-gradle-plugin/src/test/kotlin/com/jakewharton/mosaic/gradle/FixtureTest.kt b/mosaic-gradle-plugin/src/test/kotlin/com/jakewharton/mosaic/gradle/FixtureTest.kt index 24be72a21..450accc0a 100644 --- a/mosaic-gradle-plugin/src/test/kotlin/com/jakewharton/mosaic/gradle/FixtureTest.kt +++ b/mosaic-gradle-plugin/src/test/kotlin/com/jakewharton/mosaic/gradle/FixtureTest.kt @@ -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 '' or '::' + |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 = 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) } }