Browse Source
By default, the Compose Multiplatform Gradle plugin uses `org.jetbrains.compose.compiler:compiler:<COMPOSE_VERSION>` as a compiler plugin . However, a new version of Kotlin might be incompatible with the default version of compiler plugin. Previously, that forced users to update to a new version of Compose Gradle plugin & Compose libraries in order to use a new version of Kotlin. Accordingly, Compose framework developers had to release a new version of all libraries, when a new version of Kotlin is released. Some time ago the Compose team at Google made it possible to update a compiler plugin without updating the Gradle plugin and/or all Compose libraries https://developer.android.com/jetpack/androidx/releases/compose-kotlin This change allows to specify a custom compiler artifact by using the following DSL: ``` compose { kotlinCompilerPlugin.set("<VERSION<") // or kotlinCompilerPlugin.set("<GROUP_ID>:<ARTIFACT_ID>:<VERSION>") } ```pull/2353/head
Alexey Tsvetkov
2 years ago
committed by
GitHub
41 changed files with 451 additions and 110 deletions
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.internal |
||||||
|
|
||||||
|
import org.jetbrains.compose.ComposeBuildConfig |
||||||
|
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact |
||||||
|
|
||||||
|
internal class ComposeCompilerArtifactProvider(private val customPluginString: () -> String?) { |
||||||
|
val compilerArtifact: SubpluginArtifact |
||||||
|
get() { |
||||||
|
val customPlugin = customPluginString() |
||||||
|
val customCoordinates = customPlugin?.split(":") |
||||||
|
return when (customCoordinates?.size) { |
||||||
|
null -> DefaultCompiler.pluginArtifact |
||||||
|
1 -> { |
||||||
|
val customVersion = customCoordinates[0] |
||||||
|
check(customVersion.isNotBlank()) { "'compose.kotlinCompilerPlugin' cannot be blank!" } |
||||||
|
DefaultCompiler.pluginArtifact.copy(version = customVersion) |
||||||
|
} |
||||||
|
3 -> DefaultCompiler.pluginArtifact.copy( |
||||||
|
groupId = customCoordinates[0], |
||||||
|
artifactId = customCoordinates[1], |
||||||
|
version = customCoordinates[2] |
||||||
|
) |
||||||
|
else -> error(""" |
||||||
|
Illegal format of 'compose.kotlinCompilerPlugin' property. |
||||||
|
Expected format: either '<VERSION>' or '<GROUP_ID>:<ARTIFACT_ID>:<VERSION>' |
||||||
|
Actual value: '$customPlugin' |
||||||
|
""".trimIndent()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val compilerHostedArtifact: SubpluginArtifact |
||||||
|
get() = compilerArtifact.run { |
||||||
|
val newArtifactId = |
||||||
|
if (groupId == DefaultCompiler.GROUP_ID && artifactId == DefaultCompiler.ARTIFACT_ID) { |
||||||
|
DefaultCompiler.HOSTED_ARTIFACT_ID |
||||||
|
} else artifactId |
||||||
|
|
||||||
|
copy(artifactId = newArtifactId) |
||||||
|
} |
||||||
|
|
||||||
|
internal object DefaultCompiler { |
||||||
|
const val GROUP_ID = "org.jetbrains.compose.compiler" |
||||||
|
const val ARTIFACT_ID = "compiler" |
||||||
|
const val HOSTED_ARTIFACT_ID = "compiler-hosted" |
||||||
|
const val VERSION = ComposeBuildConfig.composeCompilerVersion |
||||||
|
|
||||||
|
val pluginArtifact: SubpluginArtifact |
||||||
|
get() = SubpluginArtifact(groupId = GROUP_ID, artifactId = ARTIFACT_ID, version = VERSION) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal fun SubpluginArtifact.copy( |
||||||
|
groupId: String? = null, |
||||||
|
artifactId: String? = null, |
||||||
|
version: String? = null |
||||||
|
): SubpluginArtifact = |
||||||
|
SubpluginArtifact( |
||||||
|
groupId = groupId ?: this.groupId, |
||||||
|
artifactId = artifactId ?: this.artifactId, |
||||||
|
version = version ?: this.version |
||||||
|
) |
@ -1,11 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
|
||||||
*/ |
|
||||||
|
|
||||||
package org.jetbrains.compose.test |
|
||||||
|
|
||||||
@Suppress("EnumEntryName") |
|
||||||
enum class TestKotlinVersion(val versionString: String) { |
|
||||||
Default(TestProperties.defaultKotlinVersion) |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
|
||||||
*/ |
|
||||||
|
|
||||||
package org.jetbrains.compose.test |
|
||||||
|
|
||||||
object TestProperties { |
|
||||||
val defaultKotlinVersion: String |
|
||||||
get() = notNullSystemProperty("kotlin.version") |
|
||||||
|
|
||||||
val composeVersion: String |
|
||||||
get() = notNullSystemProperty("compose.plugin.version") |
|
||||||
|
|
||||||
val gradleVersionForTests: String? |
|
||||||
get() = System.getProperty("gradle.version.for.tests") |
|
||||||
|
|
||||||
private fun notNullSystemProperty(property: String): String = |
|
||||||
System.getProperty(property) ?: error("The '$property' system property is not set") |
|
||||||
} |
|
@ -1,25 +1,23 @@ |
|||||||
/* |
/* |
||||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
*/ |
*/ |
||||||
|
|
||||||
package org.jetbrains.compose.gradle |
package org.jetbrains.compose.test.tests.integration |
||||||
|
|
||||||
import org.gradle.testkit.runner.TaskOutcome |
|
||||||
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewLogger |
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewLogger |
||||||
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.RemoteConnection |
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.RemoteConnection |
||||||
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.receiveConfigFromGradle |
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.receiveConfigFromGradle |
||||||
import org.jetbrains.compose.test.GradlePluginTestBase |
import org.jetbrains.compose.test.utils.* |
||||||
import org.jetbrains.compose.test.TestKotlinVersion |
|
||||||
import org.jetbrains.compose.test.TestProjects |
|
||||||
import org.jetbrains.compose.test.checks |
|
||||||
import org.junit.jupiter.api.Test |
|
||||||
import java.net.ServerSocket |
import java.net.ServerSocket |
||||||
import java.net.Socket |
import java.net.Socket |
||||||
import java.net.SocketTimeoutException |
import java.net.SocketTimeoutException |
||||||
import java.util.concurrent.atomic.AtomicBoolean |
import java.util.concurrent.atomic.AtomicBoolean |
||||||
import java.util.concurrent.atomic.AtomicInteger |
import java.util.concurrent.atomic.AtomicInteger |
||||||
import kotlin.concurrent.thread |
import kotlin.concurrent.thread |
||||||
|
import org.gradle.testkit.runner.TaskOutcome |
||||||
|
import org.junit.jupiter.api.Test |
||||||
|
|
||||||
class GradlePluginTest : GradlePluginTestBase() { |
class GradlePluginTest : GradlePluginTestBase() { |
||||||
@Test |
@Test |
@ -0,0 +1,107 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.test.tests.unit |
||||||
|
|
||||||
|
import org.jetbrains.compose.internal.ComposeCompilerArtifactProvider |
||||||
|
import org.jetbrains.compose.internal.copy |
||||||
|
import org.jetbrains.compose.test.utils.TestProperties |
||||||
|
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact |
||||||
|
import org.junit.jupiter.api.Test |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.* |
||||||
|
|
||||||
|
internal class ComposeCompilerArtifactProviderTest { |
||||||
|
@Test |
||||||
|
fun defaultCompilerArtifact() { |
||||||
|
assertArtifactEquals( |
||||||
|
Expected.jbCompiler, |
||||||
|
Actual.compiler(null) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun defaultCompilerHostedArtifact() { |
||||||
|
assertArtifactEquals( |
||||||
|
Expected.jbCompilerHosted, |
||||||
|
Actual.compilerHosted(null) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun customVersion() { |
||||||
|
assertArtifactEquals( |
||||||
|
Expected.jbCompiler.copy(version = "10.20.30"), |
||||||
|
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") |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun customCompilerHosted() { |
||||||
|
// 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") |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun illegalCompiler() { |
||||||
|
testIllegalCompiler("androidx.compose.compiler:compiler") |
||||||
|
testIllegalCompiler("a:b:c:d") |
||||||
|
testIllegalCompiler("") |
||||||
|
} |
||||||
|
|
||||||
|
private fun testIllegalCompiler(pluginString: String?) { |
||||||
|
try { |
||||||
|
Actual.compiler(pluginString) |
||||||
|
} catch (e: Exception) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
error("Expected error, but illegal value was accepted: '$pluginString'") |
||||||
|
} |
||||||
|
|
||||||
|
object Actual { |
||||||
|
fun compiler(pluginString: String?) = |
||||||
|
ComposeCompilerArtifactProvider { pluginString }.compilerArtifact |
||||||
|
|
||||||
|
fun compilerHosted(pluginString: String?) = |
||||||
|
ComposeCompilerArtifactProvider { pluginString }.compilerHostedArtifact |
||||||
|
} |
||||||
|
|
||||||
|
object Expected { |
||||||
|
val jbCompiler: SubpluginArtifact |
||||||
|
get() = SubpluginArtifact( |
||||||
|
groupId = "org.jetbrains.compose.compiler", |
||||||
|
artifactId = "compiler", |
||||||
|
version = TestProperties.composeCompilerVersion |
||||||
|
) |
||||||
|
|
||||||
|
val jbCompilerHosted: SubpluginArtifact |
||||||
|
get() = jbCompiler.copy(artifactId = "compiler-hosted") |
||||||
|
|
||||||
|
val googleCompiler: SubpluginArtifact |
||||||
|
get() = jbCompiler.copy(groupId = "androidx.compose.compiler") |
||||||
|
} |
||||||
|
|
||||||
|
private fun assertArtifactEquals( |
||||||
|
expected: SubpluginArtifact, |
||||||
|
actual: SubpluginArtifact |
||||||
|
) { |
||||||
|
assertEquals(expected.asString(), actual.asString()) |
||||||
|
} |
||||||
|
|
||||||
|
private fun SubpluginArtifact.asString(): String = |
||||||
|
"SubpluginArtifact(groupId = '$groupId', artifactId = '$artifactId', version = '$version')" |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.test.utils |
||||||
|
|
||||||
|
@Suppress("EnumEntryName") |
||||||
|
enum class TestKotlinVersion(val versionString: String) { |
||||||
|
Default(TestProperties.composeCompilerCompatibleKotlinVersion), |
||||||
|
AndroidxCompatible(TestProperties.androidxCompilerCompatibleKotlinVersion) |
||||||
|
} |
@ -1,14 +1,15 @@ |
|||||||
/* |
/* |
||||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
*/ |
*/ |
||||||
|
|
||||||
package org.jetbrains.compose.test |
package org.jetbrains.compose.test.utils |
||||||
|
|
||||||
object TestProjects { |
object TestProjects { |
||||||
const val jvm = "application/jvm" |
const val jvm = "application/jvm" |
||||||
const val mpp = "application/mpp" |
const val mpp = "application/mpp" |
||||||
const val proguard = "application/proguard" |
const val proguard = "application/proguard" |
||||||
|
const val androidxCompiler = "application/androidx-compiler" |
||||||
const val jvmKotlinDsl = "application/jvmKotlinDsl" |
const val jvmKotlinDsl = "application/jvmKotlinDsl" |
||||||
const val moduleClashCli = "application/moduleClashCli" |
const val moduleClashCli = "application/moduleClashCli" |
||||||
const val javaLogger = "application/javaLogger" |
const val javaLogger = "application/javaLogger" |
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.test.utils |
||||||
|
|
||||||
|
object TestProperties { |
||||||
|
val composeCompilerVersion: String |
||||||
|
get() = notNullSystemProperty("compose.compiler.version") |
||||||
|
|
||||||
|
val composeCompilerCompatibleKotlinVersion: String |
||||||
|
get() = notNullSystemProperty("compose.compiler.compatible.kotlin.version") |
||||||
|
|
||||||
|
val androidxCompilerVersion: String |
||||||
|
get() = notNullSystemProperty("compose.tests.androidx.compiler.version") |
||||||
|
|
||||||
|
val androidxCompilerCompatibleKotlinVersion: String |
||||||
|
get() = notNullSystemProperty("compose.tests.androidx.compiler.compatible.kotlin.version") |
||||||
|
|
||||||
|
val composeGradlePluginVersion: String |
||||||
|
get() = notNullSystemProperty("compose.tests.compose.gradle.plugin.version") |
||||||
|
|
||||||
|
val gradleVersionForTests: String? |
||||||
|
get() = System.getProperty("compose.tests.gradle.version") |
||||||
|
|
||||||
|
private fun notNullSystemProperty(property: String): String = |
||||||
|
System.getProperty(property) ?: error("The '$property' system property is not set") |
||||||
|
} |
@ -1,16 +1,14 @@ |
|||||||
/* |
/* |
||||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
* Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. |
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
*/ |
*/ |
||||||
|
|
||||||
package org.jetbrains.compose.test |
package org.jetbrains.compose.test.utils |
||||||
|
|
||||||
import org.gradle.internal.impldep.junit.framework.Assert |
|
||||||
import org.gradle.testkit.runner.BuildResult |
import org.gradle.testkit.runner.BuildResult |
||||||
import org.gradle.testkit.runner.TaskOutcome |
import org.gradle.testkit.runner.TaskOutcome |
||||||
import org.junit.jupiter.api.Assertions |
import org.junit.jupiter.api.Assertions |
||||||
import java.io.File |
import java.io.File |
||||||
import kotlin.math.exp |
|
||||||
|
|
||||||
internal fun <T> Collection<T>.checkContains(vararg elements: T) { |
internal fun <T> Collection<T>.checkContains(vararg elements: T) { |
||||||
val expectedElements = elements.toMutableSet() |
val expectedElements = elements.toMutableSet() |
@ -1 +1 @@ |
|||||||
junit.jupiter.displayname.generator.default=org.jetbrains.compose.test.GradleTestNameGenerator |
junit.jupiter.displayname.generator.default=org.jetbrains.compose.test.utils.GradleTestNameGenerator |
@ -0,0 +1,31 @@ |
|||||||
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat |
||||||
|
|
||||||
|
plugins { |
||||||
|
id "org.jetbrains.kotlin.jvm" |
||||||
|
id "org.jetbrains.compose" |
||||||
|
} |
||||||
|
|
||||||
|
repositories { |
||||||
|
google() |
||||||
|
jetbrainsCompose() |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib" |
||||||
|
implementation compose.desktop.currentOs |
||||||
|
} |
||||||
|
|
||||||
|
compose { |
||||||
|
kotlinCompilerPlugin.set("COMPOSE_COMPILER_ARTIFACT_PLACEHOLDER") |
||||||
|
|
||||||
|
desktop { |
||||||
|
application { |
||||||
|
mainClass = "Main" |
||||||
|
args(project.projectDir.absolutePath) |
||||||
|
nativeDistributions { |
||||||
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) |
||||||
|
} |
||||||
|
args(project.projectDir.absolutePath) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 140 B |
@ -0,0 +1,11 @@ |
|||||||
|
pluginManagement { |
||||||
|
plugins { |
||||||
|
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' |
||||||
|
id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' |
||||||
|
} |
||||||
|
repositories { |
||||||
|
mavenLocal() |
||||||
|
gradlePluginPortal() |
||||||
|
} |
||||||
|
} |
||||||
|
rootProject.name = "simple" |
@ -0,0 +1,76 @@ |
|||||||
|
import androidx.compose.foundation.background |
||||||
|
import androidx.compose.foundation.layout.* |
||||||
|
import androidx.compose.foundation.shape.CircleShape |
||||||
|
import androidx.compose.foundation.shape.GenericShape |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.ui.Alignment |
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.draw.clip |
||||||
|
import androidx.compose.ui.graphics.Color |
||||||
|
import androidx.compose.ui.graphics.Shape |
||||||
|
import androidx.compose.ui.renderComposeScene |
||||||
|
import org.jetbrains.skia.EncodedImageFormat |
||||||
|
import java.io.File |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
object Main { |
||||||
|
@JvmStatic |
||||||
|
@OptIn(ExperimentalComposeUiApi::class) |
||||||
|
fun main(args: Array<String>) { |
||||||
|
val workingDir = args.getOrNull(0)?.let { File(it) } |
||||||
|
workingDir?.mkdirs() |
||||||
|
if (workingDir == null || !workingDir.isDirectory) { |
||||||
|
error("Working directory must be passes as the first argument. '$workingDir' is not a directory") |
||||||
|
} |
||||||
|
|
||||||
|
val image = renderComposeScene(height = 10, width = 10) { |
||||||
|
mainShape() |
||||||
|
} |
||||||
|
val encodedImage = image.encodeToData(EncodedImageFormat.PNG) ?: error("Could not encode image as png") |
||||||
|
workingDir.resolve("main-image.actual.png").writeBytes(encodedImage.bytes) |
||||||
|
|
||||||
|
val mainMethods = this.javaClass.declaredMethods |
||||||
|
.mapTo(TreeSet()) { it.name } |
||||||
|
.joinToString("\n") |
||||||
|
workingDir.resolve("main-methods.actual.txt").writeText(mainMethods) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun mainShape() { |
||||||
|
triangle(Color.Magenta) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun unused() { |
||||||
|
transitivelyUnused() |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun transitivelyUnused() { |
||||||
|
triangle(Color.Gray) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun keptByKeepRule() { |
||||||
|
fillShape(Color.Blue, CircleShape) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun triangle(color: Color) { |
||||||
|
fillShape(color, GenericShape { size, _ -> |
||||||
|
moveTo(size.width / 2f, 0f) |
||||||
|
lineTo(size.width, size.height) |
||||||
|
lineTo(0f, size.height) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun fillShape(color: Color, shape: Shape){ |
||||||
|
Column(modifier = Modifier.fillMaxWidth().wrapContentSize(Alignment.Center)) { |
||||||
|
Box( |
||||||
|
modifier = Modifier.clip(shape).fillMaxSize().background(color) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue