Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gradle-plugins/buildSrc/src/main/kotlin/BuildProperties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ object BuildProperties {
fun deployVersion(project: Project): String =
System.getenv("COMPOSE_GRADLE_PLUGIN_VERSION")
?: project.findProperty("deploy.version") as String
fun hotReloadVersion(project: Project): String =
project.findProperty("hotreload.version") as String
}
68 changes: 67 additions & 1 deletion gradle-plugins/compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import de.undercouch.gradle.tasks.download.Download
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipOutputStream
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

plugins {
Expand All @@ -25,12 +29,16 @@ mavenPublicationConfig {

val buildConfigDir
get() = project.layout.buildDirectory.dir("generated/buildconfig")

val hotReloadVersion = BuildProperties.hotReloadVersion(project)

val buildConfig = tasks.register("buildConfig", GenerateBuildConfig::class.java) {
classFqName.set("org.jetbrains.compose.ComposeBuildConfig")
generatedOutputDir.set(buildConfigDir)
fieldsToGenerate.put("composeVersion", BuildProperties.composeVersion(project))
fieldsToGenerate.put("composeMaterial3Version", BuildProperties.composeMaterial3Version(project))
fieldsToGenerate.put("composeGradlePluginVersion", BuildProperties.deployVersion(project))
fieldsToGenerate.put("composeHotReloadVersion", hotReloadVersion)
}
tasks.named("compileKotlin", KotlinCompilationTask::class) {
dependsOn(buildConfig)
Expand All @@ -56,6 +64,10 @@ dependencies {
embeddedDependencies(dep)
}

fun hotReloadDep(dep: String) = embedded(
"org.jetbrains.compose.hot-reload:$dep:$hotReloadVersion"
)

compileOnly(gradleApi())
compileOnly(localGroovy())
compileOnly(kotlin("gradle-plugin"))
Expand All @@ -69,21 +81,75 @@ dependencies {

embedded(libs.download.task)
embedded(libs.kotlin.poet)
hotReloadDep("hot-reload-gradle-plugin")
hotReloadDep("hot-reload-gradle-core")
hotReloadDep("hot-reload-gradle-idea")
hotReloadDep("hot-reload-core")
hotReloadDep("hot-reload-orchestration")
hotReloadDep("hot-reload-annotations-jvm")
embedded(project(":preview-rpc"))
embedded(project(":jdk-version-probe"))
}


val packagesToRelocate = listOf("de.undercouch", "com.squareup.kotlinpoet")

val relocationPackage = "org.jetbrains.compose.internal"

val hotReloadPackage = "org.jetbrains.compose.reload"

val hotReloadPackageRelocated = "$relocationPackage.$hotReloadPackage"

val hotReloadPropertiesPath = "META-INF/gradle-plugins/org.jetbrains.compose.hot-reload.properties"

private class HotReloadPropertiesTransformer(hotReloadPackageRelocated: String) : com.github.jengelman.gradle.plugins.shadow.transformers.Transformer {
private val targetPath = "META-INF/gradle-plugins/org.jetbrains.compose.embedded.hot-reload.properties"
private val content = """
implementation-class=$hotReloadPackageRelocated.gradle.ComposeHotReloadPlugin
""".trimIndent()

override fun canTransformResource(element: FileTreeElement?): Boolean = false
override fun transform(context: TransformerContext?) = Unit
override fun hasTransformedResource(): Boolean = true

override fun modifyOutputStream(os: ZipOutputStream?, preserveFileTimestamps: Boolean) {
val entry = ZipEntry(targetPath)
entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)
os?.run {
putNextEntry(entry)
write(content.toByteArray())
closeEntry()
}
}

override fun getName(): String = "Hot reload properties transformer"
}

fun ShadowJar.relocateHotReload() {
val relocator = object : SimpleRelocator(hotReloadPackage, hotReloadPackageRelocated, ArrayList(), ArrayList()) {
override fun canRelocatePath(path: String?): Boolean {
return super.canRelocatePath(path) &&
// do not relocate orchestration as its objects are used in serialization.
path?.startsWith("org/jetbrains/compose/reload/orchestration/") == false
}
}
relocate(relocator)
}

val shadow = tasks.named<ShadowJar>("shadowJar") {
for (packageToRelocate in packagesToRelocate) {
relocate(packageToRelocate, "org.jetbrains.compose.internal.$packageToRelocate")
relocate(packageToRelocate, "$relocationPackage.$packageToRelocate")
}
relocateHotReload()

transform(HotReloadPropertiesTransformer(hotReloadPackageRelocated))

archiveBaseName.set("shadow")
archiveClassifier.set("")
archiveVersion.set("")
configurations = listOf(embeddedDependencies)
exclude("META-INF/gradle-plugins/de.undercouch.download.properties")
exclude(hotReloadPropertiesPath)
exclude("META-INF/versions/**")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ abstract class ComposePlugin : Plugin<Project> {
project.configureExperimentalTargetsFlagsCheck(mppExt)
}
}

try {
project.pluginManager.apply("org.jetbrains.compose.hot-reload")
} catch (_: Exception) {
// If a user does not set up the hot-reload plugin explicitly, set up the embedded one.
// TODO: issue a warning/error if the embedded version is higher than explicit one
project.pluginManager.apply("org.jetbrains.compose.embedded.hot-reload")
}
}

@Suppress("DEPRECATION")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.jetbrains.compose.test.tests.integration

import org.gradle.testkit.runner.BuildResult
import org.jetbrains.compose.ComposeBuildConfig
import org.jetbrains.compose.test.utils.GradlePluginTestBase
import org.jetbrains.compose.test.utils.checks
import org.jetbrains.compose.test.utils.modify
import org.junit.jupiter.api.Test
import kotlin.concurrent.thread

class HotReloadTest : GradlePluginTestBase() {
@Test
fun smokeTestHotRunTask() = with(testProject("application/jvm")) {
file("build.gradle").modify {
it + """
afterEvaluate {
tasks.getByName("hotRun").doFirst {
throw new StopExecutionException("Skip hotRun task")
}
}
""".trimIndent()
}
gradle("hotRun").checks {
check.taskSuccessful(":hotRun")
}
}

@Test
fun testHotReload() = with(testProject("application/hotReload")) {
var result: BuildResult? = null
val hotRunThread = thread {
result = gradle("hotRunJvm")
}

while (!file("started").exists()) {
Thread.sleep(200)
}

modifyText("src/jvmMain/kotlin/main.kt") {
it.replace("Kotlin MPP", "KMP")
}

gradle("reload").checks {
check.taskSuccessful(":reload")
check.logContains("MainKt.class: modified")
}

hotRunThread.join()
check(result != null)
result.checks {
check.taskSuccessful(":hotRunJvm")
check.logContains("Kotlin MPP app is running!")
check.logContains("KMP app is running!")
check.logContains("Compose Hot Reload (${ComposeBuildConfig.composeHotReloadVersion})")
}
}

@Test
fun testExternalHotReload() = with(testProject("application/mpp")) {
val externalHotReloadVersion = "1.0.0-beta04"
modifyText("settings.gradle") {
it.replace(
"plugins {", "plugins {\n" +
"""
id 'org.jetbrains.compose.hot-reload' version '$externalHotReloadVersion'
""".trimIndent()
)
}
modifyText("build.gradle") {
it.replace(
"plugins {", "plugins {\n" +
"""
id "org.jetbrains.compose.hot-reload"
""".trimIndent()
)
}
gradle("hotRunJvm").checks {
check.taskSuccessful(":hotRunJvm")
check.logContains("Compose Hot Reload ($externalHotReloadVersion)")
check.logContains("Kotlin MPP app is running!")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
id "com.android.application"
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.compose"
id "org.jetbrains.compose"
}

kotlin {
// empty stub (no actual android app) to detect configuration conflicts
// like https://github.com/JetBrains/compose-jb/issues/2345
androidTarget()

jvm()
sourceSets {
jvmMain {
dependsOn(commonMain)

dependencies {
implementation(compose.desktop.currentOs)
}
}
}
jvmToolchain {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems it will require JDK 11 to be installed that won't be a case on CI soon.
I'd suggest approach like here - 51a87ca

languageVersion.set(JavaLanguageVersion.of(11))
}
}

android {
namespace = "org.jetbrains.compose.testapp"
compileSdk = 35

defaultConfig {
minSdk = 23
targetSdk = 35
}
}

compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)

packageVersion = "1.0.0"
packageName = "TestPackage"
description = "Test description"
copyright = "Test Copyright Holder"
vendor = "Test Vendor"

linux {
shortcut = true
packageName = "test-package"
debMaintainer = "example@example.com"
menuGroup = "menu-group"
}
windows {
console = true
dirChooser = true
perUserInstall = true
shortcut = true
menu = true
menuGroup = "compose"
upgradeUuid = "2d6ff464-75be-40ad-a256-56420b9cc374"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
android.useAndroidX=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
pluginManagement {
plugins {
id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
id 'com.android.application' version 'AGP_VERSION_PLACEHOLDER'
}
repositories {
mavenLocal()
gradlePluginPortal()
mavenCentral()
google()
maven {
url 'https://maven.pkg.jetbrains.space/public/p/compose/dev'
}
maven {
url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/'
}
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
maven {
url 'https://maven.pkg.jetbrains.space/public/p/compose/dev'
}
maven {
url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/'
}
mavenLocal()
}
}
rootProject.name = "mpp"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import java.io.File

fun message() = "Kotlin MPP app is running!"

fun main() {
println(message())
File("started").createNewFile()
//wait for reload
while(!message().startsWith("KMP")){
Thread.sleep(200)
}
println(message())
}
1 change: 1 addition & 0 deletions gradle-plugins/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ compose.tests.gradle-agp.exclude=8.7/8.9.0, 8.7/9.0.0-alpha01
# A version of Gradle plugin, that will be published,
# unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var.
deploy.version=9999.0.0-SNAPSHOT
hotreload.version=1.0.0-beta08
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess Ii's supposed to be configured on CI, here is the place only for some placeholder

cc @igordmn

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to configure it only here, not on CI.

Because it is a dependency version, not a version of the building component.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is usually better to define such versions in lib.versions.toml, not in gradle.properties