Skip to content

Commit a800d3f

Browse files
committed
Bundle Compose Hot Reload Gradle plugin into the Compose Gradle plugin
1 parent 7eae205 commit a800d3f

File tree

9 files changed

+278
-1
lines changed

9 files changed

+278
-1
lines changed

gradle-plugins/buildSrc/src/main/kotlin/BuildProperties.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ object BuildProperties {
2323
fun deployVersion(project: Project): String =
2424
System.getenv("COMPOSE_GRADLE_PLUGIN_VERSION")
2525
?: project.findProperty("deploy.version") as String
26+
fun hotReloadVersion(project: Project): String =
27+
project.findProperty("hotreload.version") as String
2628
}

gradle-plugins/compose/build.gradle.kts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator
12
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
3+
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
24
import de.undercouch.gradle.tasks.download.Download
5+
import org.apache.tools.zip.ZipEntry
6+
import org.apache.tools.zip.ZipOutputStream
37
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
48

59
plugins {
@@ -25,12 +29,16 @@ mavenPublicationConfig {
2529

2630
val buildConfigDir
2731
get() = project.layout.buildDirectory.dir("generated/buildconfig")
32+
33+
val hotReloadVersion = BuildProperties.hotReloadVersion(project)
34+
2835
val buildConfig = tasks.register("buildConfig", GenerateBuildConfig::class.java) {
2936
classFqName.set("org.jetbrains.compose.ComposeBuildConfig")
3037
generatedOutputDir.set(buildConfigDir)
3138
fieldsToGenerate.put("composeVersion", BuildProperties.composeVersion(project))
3239
fieldsToGenerate.put("composeMaterial3Version", BuildProperties.composeMaterial3Version(project))
3340
fieldsToGenerate.put("composeGradlePluginVersion", BuildProperties.deployVersion(project))
41+
fieldsToGenerate.put("composeHotReloadVersion", hotReloadVersion)
3442
}
3543
tasks.named("compileKotlin", KotlinCompilationTask::class) {
3644
dependsOn(buildConfig)
@@ -56,6 +64,10 @@ dependencies {
5664
embeddedDependencies(dep)
5765
}
5866

67+
fun hotReloadDep(dep: String) = embedded(
68+
"org.jetbrains.compose.hot-reload:$dep:$hotReloadVersion"
69+
)
70+
5971
compileOnly(gradleApi())
6072
compileOnly(localGroovy())
6173
compileOnly(kotlin("gradle-plugin"))
@@ -69,21 +81,75 @@ dependencies {
6981

7082
embedded(libs.download.task)
7183
embedded(libs.kotlin.poet)
84+
hotReloadDep("hot-reload-gradle-plugin")
85+
hotReloadDep("hot-reload-gradle-core")
86+
hotReloadDep("hot-reload-gradle-idea")
87+
hotReloadDep("hot-reload-core")
88+
hotReloadDep("hot-reload-orchestration")
89+
hotReloadDep("hot-reload-annotations-jvm")
7290
embedded(project(":preview-rpc"))
7391
embedded(project(":jdk-version-probe"))
7492
}
7593

94+
7695
val packagesToRelocate = listOf("de.undercouch", "com.squareup.kotlinpoet")
7796

97+
val relocationPackage = "org.jetbrains.compose.internal"
98+
99+
val hotReloadPackage = "org.jetbrains.compose.reload"
100+
101+
val hotReloadPackageRelocated = "$relocationPackage.$hotReloadPackage"
102+
103+
val hotReloadPropertiesPath = "META-INF/gradle-plugins/org.jetbrains.compose.hot-reload.properties"
104+
105+
private class HotReloadPropertiesTransformer(hotReloadPackageRelocated: String) : com.github.jengelman.gradle.plugins.shadow.transformers.Transformer {
106+
private val targetPath = "META-INF/gradle-plugins/org.jetbrains.compose.embedded.hot-reload.properties"
107+
private val content = """
108+
implementation-class=$hotReloadPackageRelocated.gradle.ComposeHotReloadPlugin
109+
""".trimIndent()
110+
111+
override fun canTransformResource(element: FileTreeElement?): Boolean = false
112+
override fun transform(context: TransformerContext?) = Unit
113+
override fun hasTransformedResource(): Boolean = true
114+
115+
override fun modifyOutputStream(os: ZipOutputStream?, preserveFileTimestamps: Boolean) {
116+
val entry = ZipEntry(targetPath)
117+
entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)
118+
os?.run {
119+
putNextEntry(entry)
120+
write(content.toByteArray())
121+
closeEntry()
122+
}
123+
}
124+
125+
override fun getName(): String = "Hot reload properties transformer"
126+
}
127+
128+
fun ShadowJar.relocateHotReload() {
129+
val relocator = object : SimpleRelocator(hotReloadPackage, hotReloadPackageRelocated, ArrayList(), ArrayList()) {
130+
override fun canRelocatePath(path: String?): Boolean {
131+
return super.canRelocatePath(path) &&
132+
// do not relocate orchestration as its objects are used in serialization.
133+
path?.startsWith("org/jetbrains/compose/reload/orchestration/") == false
134+
}
135+
}
136+
relocate(relocator)
137+
}
138+
78139
val shadow = tasks.named<ShadowJar>("shadowJar") {
79140
for (packageToRelocate in packagesToRelocate) {
80-
relocate(packageToRelocate, "org.jetbrains.compose.internal.$packageToRelocate")
141+
relocate(packageToRelocate, "$relocationPackage.$packageToRelocate")
81142
}
143+
relocateHotReload()
144+
145+
transform(HotReloadPropertiesTransformer(hotReloadPackageRelocated))
146+
82147
archiveBaseName.set("shadow")
83148
archiveClassifier.set("")
84149
archiveVersion.set("")
85150
configurations = listOf(embeddedDependencies)
86151
exclude("META-INF/gradle-plugins/de.undercouch.download.properties")
152+
exclude(hotReloadPropertiesPath)
87153
exclude("META-INF/versions/**")
88154
}
89155

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ abstract class ComposePlugin : Plugin<Project> {
6464
project.configureExperimentalTargetsFlagsCheck(mppExt)
6565
}
6666
}
67+
68+
try {
69+
project.pluginManager.apply("org.jetbrains.compose.hot-reload")
70+
} catch (_: Exception) {
71+
// If a user does not set up the hot-reload plugin explicitly, set up the embedded one.
72+
// TODO: issue a warning/error if the embedded version is higher than explicit one
73+
project.pluginManager.apply("org.jetbrains.compose.embedded.hot-reload")
74+
}
6775
}
6876

6977
@Suppress("DEPRECATION")
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.jetbrains.compose.test.tests.integration
2+
3+
import org.gradle.testkit.runner.BuildResult
4+
import org.jetbrains.compose.ComposeBuildConfig
5+
import org.jetbrains.compose.test.utils.GradlePluginTestBase
6+
import org.jetbrains.compose.test.utils.checks
7+
import org.jetbrains.compose.test.utils.modify
8+
import org.junit.jupiter.api.Test
9+
import kotlin.concurrent.thread
10+
11+
class HotReloadTest : GradlePluginTestBase() {
12+
@Test
13+
fun smokeTestHotRunTask() = with(testProject("application/jvm")) {
14+
file("build.gradle").modify {
15+
it + """
16+
afterEvaluate {
17+
tasks.getByName("hotRun").doFirst {
18+
throw new StopExecutionException("Skip hotRun task")
19+
}
20+
}
21+
""".trimIndent()
22+
}
23+
gradle("hotRun").checks {
24+
check.taskSuccessful(":hotRun")
25+
}
26+
}
27+
28+
@Test
29+
fun testHotReload() = with(testProject("application/hotReload")) {
30+
var result: BuildResult? = null
31+
val hotRunThread = thread {
32+
result = gradle("hotRunJvm")
33+
}
34+
35+
while (!file("started").exists()) {
36+
Thread.sleep(200)
37+
}
38+
39+
modifyText("src/jvmMain/kotlin/main.kt") {
40+
it.replace("Kotlin MPP", "KMP")
41+
}
42+
43+
gradle("reload").checks {
44+
check.taskSuccessful(":reload")
45+
check.logContains("MainKt.class: modified")
46+
}
47+
48+
hotRunThread.join()
49+
check(result != null)
50+
result.checks {
51+
check.taskSuccessful(":hotRunJvm")
52+
check.logContains("Kotlin MPP app is running!")
53+
check.logContains("KMP app is running!")
54+
check.logContains("Compose Hot Reload (${ComposeBuildConfig.composeHotReloadVersion})")
55+
}
56+
}
57+
58+
@Test
59+
fun testExternalHotReload() = with(testProject("application/mpp")) {
60+
val externalHotReloadVersion = "1.0.0-beta04"
61+
modifyText("settings.gradle") {
62+
it.replace(
63+
"plugins {", "plugins {\n" +
64+
"""
65+
id 'org.jetbrains.compose.hot-reload' version '$externalHotReloadVersion'
66+
""".trimIndent()
67+
)
68+
}
69+
modifyText("build.gradle") {
70+
it.replace(
71+
"plugins {", "plugins {\n" +
72+
"""
73+
id "org.jetbrains.compose.hot-reload"
74+
""".trimIndent()
75+
)
76+
}
77+
gradle("hotRunJvm").checks {
78+
check.taskSuccessful(":hotRunJvm")
79+
check.logContains("Compose Hot Reload ($externalHotReloadVersion)")
80+
check.logContains("Kotlin MPP app is running!")
81+
}
82+
}
83+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2+
3+
plugins {
4+
id "com.android.application"
5+
id "org.jetbrains.kotlin.multiplatform"
6+
id "org.jetbrains.kotlin.plugin.compose"
7+
id "org.jetbrains.compose"
8+
}
9+
10+
kotlin {
11+
// empty stub (no actual android app) to detect configuration conflicts
12+
// like https://github.com/JetBrains/compose-jb/issues/2345
13+
androidTarget()
14+
15+
jvm()
16+
sourceSets {
17+
jvmMain {
18+
dependsOn(commonMain)
19+
20+
dependencies {
21+
implementation(compose.desktop.currentOs)
22+
}
23+
}
24+
}
25+
jvmToolchain {
26+
languageVersion.set(JavaLanguageVersion.of(11))
27+
}
28+
}
29+
30+
android {
31+
namespace = "org.jetbrains.compose.testapp"
32+
compileSdk = 35
33+
34+
defaultConfig {
35+
minSdk = 23
36+
targetSdk = 35
37+
}
38+
}
39+
40+
compose.desktop {
41+
application {
42+
mainClass = "MainKt"
43+
nativeDistributions {
44+
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
45+
46+
packageVersion = "1.0.0"
47+
packageName = "TestPackage"
48+
description = "Test description"
49+
copyright = "Test Copyright Holder"
50+
vendor = "Test Vendor"
51+
52+
linux {
53+
shortcut = true
54+
packageName = "test-package"
55+
debMaintainer = "[email protected]"
56+
menuGroup = "menu-group"
57+
}
58+
windows {
59+
console = true
60+
dirChooser = true
61+
perUserInstall = true
62+
shortcut = true
63+
menu = true
64+
menuGroup = "compose"
65+
upgradeUuid = "2d6ff464-75be-40ad-a256-56420b9cc374"
66+
}
67+
}
68+
}
69+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
android.useAndroidX=true
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
pluginManagement {
2+
plugins {
3+
id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER'
4+
id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER'
5+
id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
6+
id 'com.android.application' version 'AGP_VERSION_PLACEHOLDER'
7+
}
8+
repositories {
9+
mavenLocal()
10+
gradlePluginPortal()
11+
mavenCentral()
12+
google()
13+
maven {
14+
url 'https://maven.pkg.jetbrains.space/public/p/compose/dev'
15+
}
16+
maven {
17+
url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/'
18+
}
19+
}
20+
}
21+
dependencyResolutionManagement {
22+
repositories {
23+
mavenCentral()
24+
google()
25+
maven {
26+
url 'https://maven.pkg.jetbrains.space/public/p/compose/dev'
27+
}
28+
maven {
29+
url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/'
30+
}
31+
mavenLocal()
32+
}
33+
}
34+
rootProject.name = "mpp"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import java.io.File
2+
3+
fun message() = "Kotlin MPP app is running!"
4+
5+
fun main() {
6+
println(message())
7+
File("started").createNewFile()
8+
//wait for reload
9+
while(!message().startsWith("KMP")){
10+
Thread.sleep(200)
11+
}
12+
println(message())
13+
}

gradle-plugins/gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ compose.tests.gradle-agp.exclude=8.7/8.9.0, 8.7/9.0.0-alpha01
2525
# A version of Gradle plugin, that will be published,
2626
# unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var.
2727
deploy.version=9999.0.0-SNAPSHOT
28+
hotreload.version=1.0.0-beta08

0 commit comments

Comments
 (0)