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 {
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 = "[email protected]"
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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have to have a place for version declaration that I can use in build script as well as in tests. I can't use lib.versions.toml from tests. I see that versions are exported to sources via ComposeBuildConfig generation. And ComposeBuildConfig is generated based on gradle.properties. Though I can use lib.versions.toml from build.gradle.kts and export it via ComposeBuildConfig but, I am not sure here because it won't be aligned with other declarations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Though I can use lib.versions.toml from build.gradle.kts and export it via ComposeBuildConfig but, I am not sure here because it won't be aligned with other declarations.

It is not so important, but I suggested this because of the different nature from the build perspective - HotReload is hardcoded dependency that is built separately, Compose/Material3 on the other hand are non-hardcoded components passed from CI. That is why, it is probably better to have them configured differently.

From user (not build) perspective, they probably have the same nature - they are all versions of something provided by the plugin. That is why, it is probably better to have them in one place in ComposeBuildConfig.

The edge between component/dependency and hardcoded/passed from CI is thin, so any solution in the end should work, choose one you think is better.

Loading